Engine specific calculation.
40 {
41
42
43
45 "NumericalIntegrationIndexCdsOptionEngine::doCalc(): index recovery is not given.");
46
47
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;
55 Real underlyingNpv =
57
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);
66 results_.additionalResults[
"callPut"] =
67 arguments_.swap->side() == Protection::Buyer ? std::string(
"Call") : std::string(
"Put");
68
69
70
71
73
74
75
76 results_.additionalResults[
"Model"] = std::string(
"LognormalPriceVolatility");
77
78
79
80 Real strikePrice;
82
84 } else {
89 }
90
91 results_.additionalResults[
"strikePrice"] = strikePrice;
92
93
94
97 Real stdDev =
volatility * std::sqrt(exerciseTime);
99 results_.additionalResults[
"standardDeviation"] = stdDev;
100
101
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
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
119 blackFormula(
arguments_.swap->side() == Protection::Buyer ? Option::Put : Option::Call,
120 strikePrice, forwardPrice, stdDev, discTradeCollToExercise);
121
122 } else {
123
124
125
126 results_.additionalResults[
"Model"] = std::string(
"LognormalSpreadVolatility");
127
128
129
131 Real averageInterestRate =
132 -std::log(tmpDisc->discount(
arguments_.swap->maturity()) / tmpDisc->discount(exerciseDate)) /
133 (maturityTime - exerciseTime);
134
135
136
137 Real strikeAdjustment;
144 }
145 results_.additionalResults[
"strikeAdjustment"] = strikeAdjustment;
146
147
148
149 Real strikeSpread;
153 } else {
154 Brent brent;
155 brent.setLowerBound(1.0E-8);
156 auto strikeTarget = [this, strikeAdjustment](Real strikeSpread) {
158 strikeAdjustment;
159 };
160 try {
161 strikeSpread = brent.solve(strikeTarget, 1.0E-7,
arguments_.swap->fairSpreadClean(), 0.0001);
162
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 "
169 }
170 }
171
174
175 results_.additionalResults[
"strikeSpread"] = strikeSpread;
176
177
178
181 Real stdDev =
volatility * std::sqrt(exerciseTime);
183 results_.additionalResults[
"standardDeviation"] = stdDev;
184
185
186
187 Real forwardPriceExclFep = 1.0 -
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
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
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
228
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;
242
243
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) +
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
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 {
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) +
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 }
291
292}
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
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Real periodToTime(const Period &p)
Swap::arguments * arguments_