Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
Public Member Functions | Private Member Functions | List of all members
NumericalIntegrationIndexCdsOptionEngine Class Reference

#include <qle/pricingengines/numericalintegrationindexcdsoptionengine.hpp>

+ Inheritance diagram for NumericalIntegrationIndexCdsOptionEngine:
+ Collaboration diagram for NumericalIntegrationIndexCdsOptionEngine:

Public Member Functions

 IndexCdsOptionBaseEngine (const QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > &probability, QuantLib::Real recovery, const Handle< YieldTermStructure > &discountSwapCurrency, const Handle< YieldTermStructure > &discountTradeCollateral, const QuantLib::Handle< QuantExt::CreditVolCurve > &volatility)
 Constructor taking a default probability term structure bootstrapped from the index spreads. More...
 
 IndexCdsOptionBaseEngine (const std::vector< QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > > &probabilities, const std::vector< QuantLib::Real > &recoveries, const Handle< YieldTermStructure > &discountSwapCurrency, const Handle< YieldTermStructure > &discountTradeCollateral, const QuantLib::Handle< QuantExt::CreditVolCurve > &volatility, QuantLib::Real indexRecovery=QuantLib::Null< QuantLib::Real >())
 
- Public Member Functions inherited from IndexCdsOptionBaseEngine
 IndexCdsOptionBaseEngine (const QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > &probability, QuantLib::Real recovery, const Handle< YieldTermStructure > &discountSwapCurrency, const Handle< YieldTermStructure > &discountTradeCollateral, const QuantLib::Handle< QuantExt::CreditVolCurve > &volatility)
 Constructor taking a default probability term structure bootstrapped from the index spreads. More...
 
 IndexCdsOptionBaseEngine (const std::vector< QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > > &probabilities, const std::vector< QuantLib::Real > &recoveries, const Handle< YieldTermStructure > &discountSwapCurrency, const Handle< YieldTermStructure > &discountTradeCollateral, const QuantLib::Handle< QuantExt::CreditVolCurve > &volatility, QuantLib::Real indexRecovery=QuantLib::Null< QuantLib::Real >())
 
const std::vector< QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > > & probabilities () const
 
const std::vector< QuantLib::Real > & recoveries () const
 
const QuantLib::Handle< QuantLib::YieldTermStructure > discountSwapCurrency () const
 
const QuantLib::Handle< QuantLib::YieldTermStructure > discountTradeCollateral () const
 
const QuantLib::Handle< QuantExt::CreditVolCurvevolatility () const
 
void calculate () const override
 

Private Member Functions

void doCalc () const override
 Engine specific calculation. More...
 
Real forwardRiskyAnnuityStrike (const Real strike) const
 

Additional Inherited Members

- Protected Member Functions inherited from IndexCdsOptionBaseEngine
void registerWithMarket ()
 Register with market data. More...
 
QuantLib::Real fep () const
 Calculate the discounted value of the front end protection. More...
 
- Protected Attributes inherited from IndexCdsOptionBaseEngine
std::vector< QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > > probabilities_
 Store inputs. More...
 
std::vector< QuantLib::Real > recoveries_
 
QuantLib::Handle< QuantLib::YieldTermStructure > discountSwapCurrency_
 
QuantLib::Handle< QuantLib::YieldTermStructure > discountTradeCollateral_
 
QuantLib::Handle< QuantExt::CreditVolCurvevolatility_
 
QuantLib::Real indexRecovery_
 Assumed index recovery used in the flat strike spread curve calculation if provided. More...
 
std::vector< QuantLib::Real > notionals_
 Store the underlying index CDS notional(s) during calculation. More...
 

Detailed Description

Definition at line 29 of file numericalintegrationindexcdsoptionengine.hpp.

Member Function Documentation

◆ doCalc()

void doCalc ( ) const
overrideprivatevirtual

Engine specific calculation.

Implements IndexCdsOptionBaseEngine.

Definition at line 40 of file numericalintegrationindexcdsoptionengine.cpp.

40 {
41
42 // checks
43
44 QL_REQUIRE(indexRecovery_ != Null<Real>(),
45 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): index recovery is not given.");
46
47 // set some variables for later use
48
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;
52 Real discTradeCollToExercise = discountTradeCollateral_->discount(exerciseDate);
53 Real discSwapCurrToExercise = discountSwapCurrency_->discount(exerciseDate);
54 Real maturityTime = volatility_->timeFromReference(arguments_.swap->maturity());
55 Real underlyingNpv =
56 arguments_.swap->side() == Protection::Buyer ? arguments_.swap->NPV() : -arguments_.swap->NPV();
57
58 results_.additionalResults["runningSpread"] = arguments_.swap->runningSpread();
59 results_.additionalResults["discountToExerciseTradeCollateral"] = discTradeCollToExercise;
60 results_.additionalResults["discountToExerciseSwapCurrency"] = discSwapCurrToExercise;
61 results_.additionalResults["upfront"] =
62 underlyingNpv *
63 (arguments_.settlementType == Settlement::Cash ? discTradeCollToExercise / discSwapCurrToExercise : 1.0);
64 results_.additionalResults["valuationDateNotional"] = arguments_.swap->notional();
65 results_.additionalResults["tradeDateNotional"] = arguments_.tradeDateNtl;
66 results_.additionalResults["callPut"] =
67 arguments_.swap->side() == Protection::Buyer ? std::string("Call") : std::string("Put");
68
69 // The model that we use is driven by the vol type of the market cds vol surface, i.e. either spread vol or price
70 // vol We handle both spread or price strikes in both models.
71
73
74 // 1 price vol type model
75
76 results_.additionalResults["Model"] = std::string("LognormalPriceVolatility");
77
78 // convert spread to strike if necessary
79
80 Real strikePrice;
81 if (arguments_.strikeType == CdsOption::StrikeType::Price) {
82 // strike is expressed w.r.t. trade date notional
83 strikePrice = 1.0 - arguments_.tradeDateNtl / arguments_.swap->notional() * (1.0 - arguments_.strike);
84 } else {
85 results_.additionalResults["strikeSpread"] = arguments_.strike;
86 strikePrice = 1.0 + arguments_.tradeDateNtl / arguments_.swap->notional() *
88 (arguments_.swap->runningSpread() - arguments_.strike);
89 }
90
91 results_.additionalResults["strikePrice"] = strikePrice;
92
93 // get volatility
94
95 Real volatility = volatility_->volatility(exerciseDate, QuantExt::periodToTime(arguments_.indexTerm),
96 strikePrice, CreditVolCurve::Type::Price);
97 Real stdDev = volatility * std::sqrt(exerciseTime);
98 results_.additionalResults["volatility"] = volatility;
99 results_.additionalResults["standardDeviation"] = stdDev;
100
101 // calculate the default-adjusted forward price
102
103 Real forwardPriceExclFep = 1.0 - underlyingNpv / arguments_.swap->notional() /
104 (Settlement::Cash ? discSwapCurrToExercise : discTradeCollToExercise);
105 Real forwardPrice = forwardPriceExclFep - fep() / arguments_.swap->notional() / discTradeCollToExercise;
106
107 results_.additionalResults["fepAdjustedForwardPrice"] = forwardPrice;
108 results_.additionalResults["forwardPrice"] = forwardPriceExclFep;
109
110 // Check the inputs to the black formula before applying it
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");
117
118 results_.value = arguments_.swap->notional() *
119 blackFormula(arguments_.swap->side() == Protection::Buyer ? Option::Put : Option::Call,
120 strikePrice, forwardPrice, stdDev, discTradeCollToExercise);
121
122 } else {
123
124 // 2 spread vol type model
125
126 results_.additionalResults["Model"] = std::string("LognormalSpreadVolatility");
127
128 // compute average interest rate for underlying swap time interval
129
130 auto tmpDisc = arguments_.settlementType == Settlement::Cash ? discountSwapCurrency_ : discountTradeCollateral_;
131 Real averageInterestRate =
132 -std::log(tmpDisc->discount(arguments_.swap->maturity()) / tmpDisc->discount(exerciseDate)) /
133 (maturityTime - exerciseTime);
134
135 // compute the strike adjustment, notice that the strike adjustment is scaled by trade date notional
136
137 Real strikeAdjustment;
138 if (arguments_.strikeType == CdsOption::StrikeType::Spread) {
139 strikeAdjustment = arguments_.tradeDateNtl / arguments_.swap->notional() *
141 (arguments_.swap->runningSpread() - arguments_.strike);
142 } else if (arguments_.strikeType == CdsOption::StrikeType::Price) {
143 strikeAdjustment = arguments_.tradeDateNtl / arguments_.swap->notional() * (arguments_.strike - 1.0);
144 }
145 results_.additionalResults["strikeAdjustment"] = strikeAdjustment;
146
147 // back out spread strike from strike adjustment if necessary
148
149 Real strikeSpread;
150 if (arguments_.strikeType == CdsOption::StrikeType::Spread &&
151 QuantLib::close_enough(arguments_.tradeDateNtl, arguments_.swap->notional())) {
152 strikeSpread = arguments_.strike;
153 } else {
154 Brent brent;
155 brent.setLowerBound(1.0E-8);
156 auto strikeTarget = [this, strikeAdjustment](Real strikeSpread) {
157 return forwardRiskyAnnuityStrike(strikeSpread) * (arguments_.swap->runningSpread() - strikeSpread) -
158 strikeAdjustment;
159 };
160 try {
161 strikeSpread = brent.solve(strikeTarget, 1.0E-7, arguments_.swap->fairSpreadClean(), 0.0001);
162 // eval function at solution to make sure, add results are set correctly
163 forwardRiskyAnnuityStrike(strikeSpread);
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 "
168 << (arguments_.strikeType == CdsOption::StrikeType::Spread ? "Spread" : "Price"));
169 }
170 }
171
173 results_.additionalResults["strikePrice"] = arguments_.strike;
174
175 results_.additionalResults["strikeSpread"] = strikeSpread;
176
177 // get volatility
178
179 Real volatility = volatility_->volatility(exerciseDate, QuantExt::periodToTime(arguments_.indexTerm),
180 strikeSpread, CreditVolCurve::Type::Spread);
181 Real stdDev = volatility * std::sqrt(exerciseTime);
182 results_.additionalResults["volatility"] = volatility;
183 results_.additionalResults["standardDeviation"] = stdDev;
184
185 // calculate the default-adjusted forward price
186
187 Real forwardPriceExclFep = 1.0 -
188 underlyingNpv / arguments_.swap->notional() /
189 (Settlement::Cash ? discSwapCurrToExercise : discTradeCollToExercise);
190 Real forwardPrice = forwardPriceExclFep -
191 fep() / arguments_.swap->notional() / discTradeCollToExercise;
192
193 results_.additionalResults["fepAdjustedForwardPrice"] = forwardPrice;
194 results_.additionalResults["forwardPrice"] = forwardPriceExclFep;
195
196 // the default-adjusted index value Vc using a continuous annuity
197
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);
201 Real a = (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;
204 else
205 a *= (1.0 - std::exp(-w)) / w;
206 return (s - c) * a;
207 };
208
209 // calibrate the default-adjusted forward spread m to the forward price
210
211 SimpsonIntegral simpson = SimpsonIntegral(1.0E-7, 100);
212
213 auto target = [this, &Vc, &simpson, exerciseTime, maturityTime, averageInterestRate, stdDev,
214 forwardPrice](Real m) {
215 return simpson(
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>();
220 },
221 -10.0, 10.0) -
222 (1.0 - forwardPrice);
223 };
224
225 Real fepAdjustedForwardSpread;
226 if (target(0.0) > 0.0) {
227 // the target function might not have a zero, because of the continuous annuity approximation
228 // in some extreme situations (e.g. survival prob = 1 everywhere)
229 fepAdjustedForwardSpread = 0.0;
230 } else {
231 Brent brent;
232 brent.setLowerBound(0.0);
233 try {
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: "
237 << e.what());
238 }
239 }
240 results_.additionalResults["fepAdjustedForwardSpread"] = fepAdjustedForwardSpread;
241 results_.additionalResults["forwardSpread"] = arguments_.swap->fairSpreadClean();
242
243 // find the exercise boundary
244
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) +
249 strikeAdjustment + arguments_.realisedFep / arguments_.swap->notional()) *
250 std::exp(-0.5 * x * x) / boost::math::constants::root_two_pi<Real>();
251 };
252
253 Real exerciseBoundary;
254 Brent brent2;
255 try {
256 exerciseBoundary = brent2.solve(payoff, 1.0E-7, 0.0, 0.0001);
257 } catch (const std::exception& e) {
258 QL_FAIL(
259 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): failed to find exercise boundary: " << e.what());
260 }
261 results_.additionalResults["exerciseBoundary"] =
262 fepAdjustedForwardSpread * std::exp(-0.5 * stdDev * stdDev + stdDev * exerciseBoundary);
263
264 // compute the option value
265
266 Real lowerIntegrationBound = -10.0, upperIntegrationBound = 10.0;
267 if (arguments_.swap->side() == Protection::Buyer) {
268 lowerIntegrationBound = exerciseBoundary;
269 } else {
270 upperIntegrationBound = exerciseBoundary;
271 }
272
273 try {
274 results_.value = arguments_.swap->notional() * discTradeCollToExercise *
275 simpson(
276 [exerciseTime, maturityTime, averageInterestRate, this, stdDev,
277 fepAdjustedForwardSpread, omega, strikeAdjustment, &Vc](Real x) {
278 return omega *
279 (Vc(exerciseTime, maturityTime, averageInterestRate, indexRecovery_,
280 arguments_.swap->runningSpread(), stdDev, fepAdjustedForwardSpread, x) +
281 strikeAdjustment + arguments_.realisedFep / arguments_.swap->notional()) *
282 std::exp(-0.5 * x * x) / boost::math::constants::root_two_pi<Real>();
283 },
284 lowerIntegrationBound, upperIntegrationBound);
285 } catch (const std::exception& e) {
286 QL_FAIL(
287 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): failed to compute option payoff: " << e.what());
288 }
289
290 } // handle 2 spread vol model type
291
292} // doCalc();
const Instrument::results * results_
Definition: cdsoption.cpp:81
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_
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Real periodToTime(const Period &p)
Definition: time.cpp:37
Swap::arguments * arguments_
+ Here is the call graph for this function:

◆ forwardRiskyAnnuityStrike()

Real forwardRiskyAnnuityStrike ( const Real  strike) const
private

Definition at line 294 of file numericalintegrationindexcdsoptionengine.cpp.

294 {
295
296 // Underlying index CDS.
297 const auto& cds = *arguments_.swap;
298
299 // This method returns RPV01(0; t_e, T, K) / SP(t_e; K). This is the quantity in formula 11.9 of O'Kane 2008.
300 // There is a slight modification in that we divide by the survival probability to t_E using the flat curve at
301 // the strike spread that we create here.
302
303 // Standard index CDS schedule.
304 Schedule schedule = MakeSchedule()
305 .from(cds.protectionStartDate())
306 .to(cds.maturity())
307 .withCalendar(WeekendsOnly())
308 .withFrequency(Quarterly)
309 .withConvention(Following)
310 .withTerminationDateConvention(Unadjusted)
311 .withRule(DateGeneration::CDS2015);
312
313 // Derive hazard rate curve from a single forward starting CDS matching the characteristics of underlying index
314 // CDS with a running spread equal to the strike.
315 Real accuracy = 1e-8;
316
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());
321 // dummy engine
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()))));
326
327 Real hazardRate;
328 try {
329 hazardRate =
330 strikeCds->impliedHazardRate(0.0, discountSwapCurrency_, Actual365Fixed(), indexRecovery_, accuracy);
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());
334 }
335
336 Handle<DefaultProbabilityTermStructure> dph(
337 QuantLib::ext::make_shared<FlatHazardRate>(discountSwapCurrency_->referenceDate(), hazardRate, Actual365Fixed()));
338
339 // Calculate the forward risky strike annuity.
340 strikeCds->setPricingEngine(
341 QuantLib::ext::make_shared<QuantExt::MidPointCdsEngine>(dph, indexRecovery_, discountSwapCurrency_));
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.");
346
347 // Survival to exercise
348 const Date& exerciseDate = arguments_.exercise->dates().front();
349 Probability spToExercise = dph->survivalProbability(exerciseDate);
350 Real discToExercise = discountSwapCurrency_->discount(exerciseDate);
351 results_.additionalResults["strikeBasedSurvivalToExercise"] = spToExercise;
352
353 // Forward risky annuity strike
354 Real rpv01_K_fwd = rpv01_K / spToExercise / discToExercise;
355 results_.additionalResults["forwardRiskyAnnuityStrike"] = rpv01_K_fwd;
356
357 return rpv01_K_fwd;
358}
+ Here is the caller graph for this function:

◆ IndexCdsOptionBaseEngine() [1/2]

IndexCdsOptionBaseEngine ( const QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > &  probability,
QuantLib::Real  recovery,
const Handle< YieldTermStructure > &  discountSwapCurrency,
const Handle< YieldTermStructure > &  discountTradeCollateral,
const QuantLib::Handle< QuantExt::CreditVolCurve > &  volatility 
)

Constructor taking a default probability term structure bootstrapped from the index spreads.

◆ IndexCdsOptionBaseEngine() [2/2]

IndexCdsOptionBaseEngine ( const std::vector< QuantLib::Handle< QuantLib::DefaultProbabilityTermStructure > > &  probabilities,
const std::vector< QuantLib::Real > &  recoveries,
const Handle< YieldTermStructure > &  discountSwapCurrency,
const Handle< YieldTermStructure > &  discountTradeCollateral,
const QuantLib::Handle< QuantExt::CreditVolCurve > &  volatility,
QuantLib::Real  indexRecovery = QuantLib::Null<QuantLib::Real>() 
)

Constructor taking a vector of default probability term structures bootstrapped from the index constituent spread curves and a vector of associated recovery rates.