Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
commodityswaptionengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <iomanip>
20#include <iostream>
21#include <ql/exercise.hpp>
22#include <ql/math/matrixutilities/choleskydecomposition.hpp>
23#include <ql/math/matrixutilities/pseudosqrt.hpp>
24#include <ql/math/randomnumbers/inversecumulativerng.hpp>
25#include <ql/math/randomnumbers/mt19937uniformrng.hpp>
26#include <ql/pricingengines/blackformula.hpp>
27#include <ql/processes/ornsteinuhlenbeckprocess.hpp>
31
32using std::map;
33using std::max;
34using std::vector;
35
36namespace {
37
40using QuantLib::CashFlow;
41using QuantLib::close;
42using QuantLib::Leg;
43
44// Check that all cashflows in the 'leg' are of the type 'CommCashflow'
45// Check also that the spread is 0.0 and the gearing is 1.0. These restrictions should be easy to remove
46// but we should only spend time on this if it is needed.
47template <typename CommCashflow> void checkCashflows(const Leg& leg) {
48 for (const QuantLib::ext::shared_ptr<CashFlow>& cf : leg) {
49 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommCashflow>(cf);
50 QL_REQUIRE(ccf, "checkCashflows: not all of the "
51 << "cashflows on the commodity floating leg are of the same type");
52 QL_REQUIRE(close(ccf->spread(), 0.0), "checkCashflows: a non-zero spread on a commodity swap "
53 << "underlying a commodity swaption is not supported");
54 QL_REQUIRE(close(ccf->gearing(), 1.0), "checkCashflows: a gearing different from 1.0 on a commodity swap "
55 << "underlying a commodity swaption is not supported");
56 }
57}
58
59// If the first coupon in the leg references a commodity future price, return true. If it references a spot price
60// return false. If the leg is not a commodity leg, throw.
61bool referencesFuturePrice(const Leg& leg) {
62 QuantLib::ext::shared_ptr<CashFlow> cf = leg.front();
63 if (QuantLib::ext::shared_ptr<CommodityIndexedCashFlow> ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf)) {
64 return ccf->useFuturePrice();
65 } else if (QuantLib::ext::shared_ptr<CommodityIndexedAverageCashFlow> ccf =
66 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf)) {
67 return ccf->useFuturePrice();
68 } else {
69 QL_FAIL("referencesFuturePrice: expected leg to be a commodity leg");
70 }
71}
72
73} // namespace
74
75namespace QuantExt {
76
77CommoditySwaptionBaseEngine::CommoditySwaptionBaseEngine(const Handle<YieldTermStructure>& discountCurve,
78 const Handle<BlackVolTermStructure>& vol, Real beta)
79 : discountCurve_(discountCurve), volStructure_(vol), beta_(beta) {
80 QL_REQUIRE(beta_ >= 0.0, "beta >= 0 required, found " << beta_);
81 registerWith(discountCurve_);
82 registerWith(volStructure_);
83}
84
86
87 Size fixedLegIndex = Null<Size>();
88 bool haveFloatingLeg = false;
89
90 QL_REQUIRE(arguments_.legs.size() == 2, "Two legs expected but found " << arguments_.legs.size());
91
92 // Check both legs and populate the index of the fixed leg.
93 for (Size i = 0; i < 2; i++) {
94 QuantLib::ext::shared_ptr<CashFlow> cf = arguments_.legs[i].front();
95 if (QuantLib::ext::shared_ptr<CommodityIndexedAverageCashFlow> flow =
96 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf)) {
97 haveFloatingLeg = true;
98 checkCashflows<CommodityIndexedAverageCashFlow>(arguments_.legs[i]);
99 } else if (QuantLib::ext::shared_ptr<CommodityIndexedCashFlow> flow =
100 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf)) {
101 haveFloatingLeg = true;
102 checkCashflows<CommodityIndexedCashFlow>(arguments_.legs[i]);
103 } else {
104 fixedLegIndex = i;
105 }
106 }
107
108 QL_REQUIRE(haveFloatingLeg, "CommoditySwaptionBaseEngine: expected the swap to have a commodity floating leg");
109 QL_REQUIRE(fixedLegIndex != Null<Size>(), "CommoditySwaptionBaseEngine: expected the swap to have a fixed leg");
110
111 return fixedLegIndex;
112}
113
114Real CommoditySwaptionBaseEngine::fixedLegValue(Size fixedLegIndex) const {
115
116 // Return the commodity fixed leg value at the swaption expiry time.
117 // This is the quantity K^{*} in the ORE+ Product Catalogue.
118 Real value = 0.0;
119
120 for (const QuantLib::ext::shared_ptr<CashFlow>& cf : arguments_.legs[fixedLegIndex]) {
121 value += cf->amount() * discountCurve_->discount(cf->date());
122 }
123
124 Date exercise = arguments_.exercise->dateAt(0);
125 Real discountExercise = discountCurve_->discount(exercise);
126 value /= discountExercise;
127
128 return value;
129}
130
131Real CommoditySwaptionBaseEngine::strike(Size fixedLegIndex) const {
132
133 // First calculation period fixed leg amount
134 Real amount = arguments_.legs[fixedLegIndex].front()->amount();
135
136 // Divide by first calculation period floating leg (full calculation period) quantity
137 Size idxFloat = fixedLegIndex == 0 ? 1 : 0;
138 QuantLib::ext::shared_ptr<CashFlow> cf = arguments_.legs[idxFloat].front();
139 if (QuantLib::ext::shared_ptr<CommodityIndexedCashFlow> ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf)) {
140 return amount / ccf->periodQuantity();
141 } else if (QuantLib::ext::shared_ptr<CommodityIndexedAverageCashFlow> ccf =
142 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf)) {
143 return amount / ccf->periodQuantity();
144 } else {
145 QL_FAIL("Expected a CommodityIndexedCashFlow or CommodityIndexedAverageCashFlow");
146 }
147}
148
149Real CommoditySwaptionBaseEngine::rho(const Date& ed_1, const Date& ed_2) const {
150
151 if (beta_ == 0.0 || ed_1 == ed_2) {
152 return 1.0;
153 } else {
154 Time t_1 = volStructure_->timeFromReference(ed_1);
155 Time t_2 = volStructure_->timeFromReference(ed_2);
156 return exp(-beta_ * fabs(t_2 - t_1));
157 }
158}
159
160bool CommoditySwaptionBaseEngine::averaging(Size floatLegIndex) const {
161
162 // All cashflows in the floating leg have been checked for same type so just use first one here
163 return static_cast<bool>(
164 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(arguments_.legs[floatLegIndex].front()));
165}
166
168
169 // Get the index of the fixed and floating leg
170 Size idxFixed = fixedLegIndex();
171 Size idxFloat = idxFixed == 0 ? 1 : 0;
172
173 // Fixed leg value is the strike
174 Real kStar = fixedLegValue(idxFixed);
175
176 // Max quantity is used as a normalisation factor
177 Real normFactor = maxQuantity(idxFloat);
178
179 // E[A(t_e)] from the ORE+ Product Catalogue
180 Real EA = expA(idxFloat, normFactor);
181
182 // Fixed leg strike price. This determines the strike at which we query the volatility surface in the
183 // calculations. The implementation here just looks at the fixed price in the first period of the fixed leg. If
184 // we have an underlying swap where the fixed price varies a lot over different calculation periods, this may
185 // lead to a mispricing.
186 Real strikePrice = strike(idxFixed);
187
188 // E[A^2(t_e)] from the ORE+ Product Catalogue
189 Real EAA = expASquared(idxFloat, strikePrice, normFactor);
190
191 // Discount factor to the exercise date, P(0, t_e) from the ORE+ Product Catalogue
192 Date exercise = arguments_.exercise->dateAt(0);
193 Real discountExercise = discountCurve_->discount(exercise);
194
195 // Time to swaption expiry, t_e from the ORE+ Product Catalogue
196 Time t_e = volStructure_->timeFromReference(exercise);
197
198 // sigma_X from the ORE+ Product Catalogue
199 Real sigmaX = sqrt(log(EAA / (EA * EA)) / t_e);
200
201 // We have used EA to get sigmaX so we can modify EA again now to remove the normalisation factor.
202 EA *= normFactor;
203
204 // If fixed leg payer flag value is -1 => payer swaption. In this case, we want \omega = 1 in black formula
205 // so we need type = Call.
206 Option::Type type = arguments_.payer[idxFixed] < 0 ? Option::Call : Option::Put;
207
208 // Populate the value using Black-76 formula as documented in ORE+ Product Catalogue
209 results_.value = blackFormula(type, kStar, EA, sigmaX * sqrt(t_e), discountExercise);
210
211 // Populate some additional results
212 results_.additionalResults["Sigma"] = sigmaX;
213 results_.additionalResults["Forward"] = EA;
214 results_.additionalResults["Strike"] = kStar;
215 results_.additionalResults["StrikePrice"] = strikePrice;
216 results_.additionalResults["Expiry"] = t_e;
217}
218
219Real CommoditySwaptionEngine::expA(Size floatLegIndex, Real normFactor) const {
220
221 // This is the quantity E[A(t_e)] in the ORE+ Product Catalogue.
222 Real value = 0.0;
223
224 for (const QuantLib::ext::shared_ptr<CashFlow>& cf : arguments_.legs[floatLegIndex]) {
225 value += cf->amount() * discountCurve_->discount(cf->date()) / normFactor;
226 }
227
228 Date exercise = arguments_.exercise->dateAt(0);
229 Real discountExercise = discountCurve_->discount(exercise);
230 value /= discountExercise;
231
232 return value;
233}
234
235Real CommoditySwaptionEngine::expASquared(Size floatLegIndex, Real strike, Real normFactor) const {
236
237 // This is the quantity E[A^2(t_e)] in the ORE+ Product Catalogue.
238 Real value = 0.0;
239
240 // Is the floating leg averaging.
241 bool isAveraging = averaging(floatLegIndex);
242
243 // Calculate E[A^2(t_e)]
244 Size numPeriods = arguments_.legs[floatLegIndex].size();
245 for (Size i = 0; i < numPeriods; i++) {
246 for (Size j = 0; j <= i; j++) {
247 Real factor = i == j ? 1.0 : 2.0;
248 value += factor * crossTerms(arguments_.legs[floatLegIndex][i], arguments_.legs[floatLegIndex][j],
249 isAveraging, strike, normFactor);
250 }
251 }
252
253 // Divide through by P^2(0, t_e) to get quantity E[A^2(t_e)] in the ORE+ Product Catalogue.
254 Date exercise = arguments_.exercise->dateAt(0);
255 Real discountExercise = discountCurve_->discount(exercise);
256 value /= (discountExercise * discountExercise);
257
258 return value;
259}
260
261Real CommoditySwaptionEngine::crossTerms(const QuantLib::ext::shared_ptr<CashFlow>& cf_1,
262 const QuantLib::ext::shared_ptr<CashFlow>& cf_2, bool isAveraging, Real strike,
263 Real normFactor) const {
264
265 // Time to swaption expiry
266 Time t_e = volStructure_->timeFromReference(arguments_.exercise->dateAt(0));
267
268 // Switch depending on whether the two cashflows are averaging or not
269 if (isAveraging) {
270
271 // Must have CommodityIndexedAverageCashFlow if averaging
272 QuantLib::ext::shared_ptr<CommodityIndexedAverageCashFlow> ccf_1 =
273 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf_1);
274 QuantLib::ext::shared_ptr<CommodityIndexedAverageCashFlow> ccf_2 =
275 QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf_2);
276
277 // Product of quantities
278 Real result = ccf_1->periodQuantity() / normFactor;
279 result *= ccf_2->periodQuantity() / normFactor;
280
281 // Multiply by discount factor to payment date
282 result *= discountCurve_->discount(ccf_1->date());
283 result *= discountCurve_->discount(ccf_2->date());
284
285 // Divide by number of observations
286 result /= ccf_1->indices().size();
287 result /= ccf_2->indices().size();
288
289 // The cross terms due to the averaging in each cashflow's period. The calculation here differs
290 // depending on whether cashflows reference a future price or spot.
291 Real cross = 0.0;
292 if (ccf_1->useFuturePrice()) {
293
294 std::vector<Real> prices1(ccf_1->indices().size()), prices2(ccf_2->indices().size()),
295 vol1(ccf_1->indices().size()), vol2(ccf_2->indices().size());
296 std::vector<Date> expiries1(ccf_1->indices().size()), expiries2(ccf_2->indices().size());
297
298 Size i = 0;
299 for (const auto& [d, p] : ccf_1->indices()) {
300 expiries1[i] = p->expiryDate();
301 Real fxRate{1.};
302 if (ccf_1->fxIndex())
303 fxRate = ccf_1->fxIndex()->fixing(expiries1[i]);
304 prices1[i] = fxRate * p->fixing(expiries1[i]);
305 vol1[i] = volStructure_->blackVol(expiries1[i], strike);
306 ++i;
307 }
308
309 i = 0;
310 for (const auto& [d, p] : ccf_2->indices()) {
311 expiries2[i] = p->expiryDate();
312 Real fxRate{1.};
313 if (ccf_2->fxIndex())
314 fxRate = ccf_2->fxIndex()->fixing(expiries2[i]);
315 prices2[i] = fxRate * p->fixing(expiries2[i]);
316 vol2[i] = volStructure_->blackVol(expiries2[i], strike);
317 ++i;
318 }
319
320 for (Size i = 0; i < prices1.size(); ++i) {
321 for (Size j = 0; j < prices2.size(); ++j) {
322 cross += prices1[i] * prices2[j] * exp(rho(expiries1[i], expiries2[j]) * vol1[i] * vol2[j] * t_e);
323 }
324 }
325
326 } else {
327
328 std::vector<Real> prices1(ccf_1->indices().size()), prices2(ccf_2->indices().size());
329
330 Size i = 0;
331 for (const auto& [d, p] : ccf_1->indices()) {
332 Real fxRate{1.};
333 if (ccf_1->fxIndex())
334 fxRate = ccf_1->fxIndex()->fixing(d);
335 prices1[i] = fxRate * p->fixing(d);
336 ++i;
337 }
338
339 i = 0;
340 for (const auto& [d, p] : ccf_2->indices()) {
341 Real fxRate{1.};
342 if (ccf_2->fxIndex())
343 fxRate = ccf_2->fxIndex()->fixing(d);
344 prices2[i] = fxRate * p->fixing(d);
345 ++i;
346 }
347
348 for (Size i = 0; i < prices1.size(); ++i) {
349 for (Size j = 0; j < prices2.size(); ++j) {
350 cross += prices1[i] * prices2[j];
351 }
352 }
353
354 cross *= exp(volStructure_->blackVariance(t_e, strike));
355 }
356 result *= cross;
357
358 return result;
359
360 } else {
361
362 // Must have CommodityIndexedCashFlow if not averaging
363 QuantLib::ext::shared_ptr<CommodityIndexedCashFlow> ccf_1 = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf_1);
364 QuantLib::ext::shared_ptr<CommodityIndexedCashFlow> ccf_2 = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf_2);
365
366 // Don't support non-zero spreads or gearing != 1 so amount gives forward * quantity for commodity cashflow
367 // referencing spot price or future * quantity for commodity cashflow referencing future price
368 Real result = ccf_1->amount() / normFactor;
369 result *= ccf_2->amount() / normFactor;
370
371 // Multiply by discount factor to payment date
372 result *= discountCurve_->discount(ccf_1->date());
373 result *= discountCurve_->discount(ccf_2->date());
374
375 // Multiply by exp{v} where the quantity v depends on whether commodity is spot or future
376 Real v = 0.0;
377 if (ccf_1->useFuturePrice()) {
378
379 Date e_1 = ccf_1->index()->expiryDate();
380 v = volStructure_->blackVol(e_1, strike);
381
382 Date e_2 = ccf_2->index()->expiryDate();
383 if (e_1 == e_2) {
384 v *= v;
385 } else {
386 v *= volStructure_->blackVol(e_2, strike);
387 v *= rho(e_1, e_2);
388 }
389
390 v *= t_e;
391
392 } else {
393 v = volStructure_->blackVariance(t_e, strike);
394 }
395 result *= exp(v);
396
397 return result;
398 }
399}
400
401Real CommoditySwaptionEngine::maxQuantity(Size floatLegIndex) const {
402
403 Real result = 0.0;
404
405 if (averaging(floatLegIndex)) {
406 for (const auto& cf : arguments_.legs[floatLegIndex]) {
407 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf);
408 QL_REQUIRE(ccf, "maxQuantity: expected a CommodityIndexedAverageCashFlow");
409 result = max(result, ccf->periodQuantity());
410 }
411 } else {
412 for (const auto& cf : arguments_.legs[floatLegIndex]) {
413 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf);
414 QL_REQUIRE(ccf, "maxQuantity: expected a CommodityIndexedCashFlow");
415 result = max(result, ccf->periodQuantity());
416 }
417 }
418
419 QL_REQUIRE(result > 0.0, "maxQuantity: quantities should be greater than 0.0");
420
421 return result;
422}
423
425
426 // Get the index of the fixed and floating leg
427 Size idxFixed = fixedLegIndex();
428 Size idxFloat = idxFixed == 0 ? 1 : 0;
429
430 // Determines the strike at which we read values from the vol surface.
431 Real strikePrice = strike(idxFixed);
432
433 // Switch implementation depending on whether underlying swap references a spot or future price
434 if (referencesFuturePrice(arguments_.legs[idxFloat])) {
435 calculateFuture(idxFixed, idxFloat, strikePrice);
436 } else {
437 calculateSpot(idxFixed, idxFloat, strikePrice);
438 }
439}
440
441void CommoditySwaptionMonteCarloEngine::calculateSpot(Size idxFixed, Size idxFloat, Real strike) const {
442
443 // Fixed leg value
444 Real valueFixedLeg = fixedLegValue(idxFixed);
445
446 // If float leg payer flag is 1 (-1) => rec (pay) float and pay (rec) fixed => omega = 1 (-1) for payer (receiver).
447 Real omega = arguments_.payer[idxFloat];
448
449 // Time to swaption expiry
450 Date exercise = arguments_.exercise->dateAt(0);
451 Time t_e = volStructure_->timeFromReference(exercise);
452
453 // Discount to exercise
454 Real discountExercise = discountCurve_->discount(exercise);
455
456 // Variance of spot process to expiry
457 Real var = volStructure_->blackVariance(t_e, strike);
458 Real stdDev = sqrt(var);
459
460 // Sample spot is S_i(t_e) = F(0, t_e) exp(-var / 2) exp(stdDev z_i). factor is second two term here.
461 Real factor = exp(-var / 2.0);
462
463 // Populate these values below
464 Real optionValue = 0.0;
465 Real swapValue = 0.0;
466 Real floatLegValue = 0.0;
467
468 // Create the standard normal RNG
469 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(1, seed_);
470
471 // Get the value that when multiplied by the sample value gives the float leg value
472 Real floatFactor = spotFloatLegFactor(idxFloat, discountExercise);
473
474 // Value swap at swaption exercise date over a number, i = 1, ..., N of samples i.e. \hat{V}_i(t_e) from ORE+
475 // Product Catalogue. Value of the swaption at time 0 is then P(0, t_e) \sum_{i=1}^{N} \hat{V}^{+}_i(t_e) / N
476 for (Size i = 0; i < samples_; i++) {
477
478 // Sample value is S_i(t_e) / F(0, t_e) = exp(-var / 2) exp(stdDev z_i).
479 Real sample = factor * exp(stdDev * rsg.nextSequence().value[0]);
480
481 // Calculate float leg value and swap value given sample
482 Real sampleFloatLegValue = floatFactor * sample;
483 Real sampleSwapValue = omega * (sampleFloatLegValue - valueFixedLeg);
484
485 // Update values dividing by samples each time to guard against blow up.
486 optionValue += max(sampleSwapValue, 0.0) / samples_;
487 swapValue += sampleSwapValue / samples_;
488 floatLegValue += sampleFloatLegValue / samples_;
489 }
490
491 // Populate the results remembering to multiply by P(0, t_e)
492 results_.value = discountExercise * optionValue;
493 results_.additionalResults["SwapNPV"] = discountExercise * swapValue;
494 results_.additionalResults["FixedLegNPV"] = discountExercise * valueFixedLeg;
495 results_.additionalResults["FloatingLegNPV"] = discountExercise * floatLegValue;
496}
497
498void CommoditySwaptionMonteCarloEngine::calculateFuture(Size idxFixed, Size idxFloat, Real strike) const {
499
500 // Fixed leg value
501 Real valueFixedLeg = fixedLegValue(idxFixed);
502
503 // Unique expiry dates
504 Matrix sqrtCorr;
505 map<Date, Real> expiries = futureExpiries(idxFloat, sqrtCorr, strike);
506
507 // Generate n _independent_ standard normal variables {Z_{i,1}, ..., Z_{i,n}} where n is the number of future
508 // contracts that we are modelling and i = 1, ..., N is the number of samples. It is a speed-up to set n = 1 if
509 // correlation between all the future contracts is 1.0 i.e. beta_ = 0.0. We don't do this and prefer code clarity.
510 LowDiscrepancy::rsg_type rsg = LowDiscrepancy::make_sequence_generator(expiries.size(), seed_);
511
512 // If float leg payer flag is 1 (-1) => rec (pay) float and pay (rec) fixed => omega = 1 (-1) for payer (receiver).
513 Real omega = arguments_.payer[idxFloat];
514
515 // Time to swaption expiry
516 Date exercise = arguments_.exercise->dateAt(0);
517 Time t_e = volStructure_->timeFromReference(exercise);
518
519 // Discount to exercise
520 Real discountExercise = discountCurve_->discount(exercise);
521
522 // Precalculate exp{-var_j / 2} and stdDev_j mentioned in comment above
523 vector<Real> expVar;
524 vector<Real> stdDev;
525 vector<Date> expiryDates;
526 for (const auto& p : expiries) {
527 Real var = p.second * p.second * t_e;
528 expVar.push_back(exp(-var / 2.0));
529 stdDev.push_back(sqrt(var));
530 expiryDates.push_back(p.first);
531 }
532
533 // Populate these values below
534 Real optionValue = 0.0;
535 Real swapValue = 0.0;
536 Real floatLegValue = 0.0;
537
538 // Values that will be used to calculate floating leg value on each iteration. We precalculate as much as
539 // possible here to avoid calculation on each iteration.
540 Size numCfs = arguments_.legs[idxFloat].size();
541 Matrix factors(numCfs, expiries.size(), 0.0);
542 Array discounts(numCfs, 0.0);
543 Array amounts(numCfs, 0.0);
544 futureFloatLegFactors(idxFloat, discountExercise, expiryDates, factors, discounts, amounts);
545 Array factor = (discounts * amounts) * factors;
546
547 // Value swap at swaption exercise date over a number, i = 1, ..., N of samples i.e. \hat{V}_i(t_e) from ORE+
548 // Product Catalogue. Value of the swaption at time 0 is then P(0, t_e) \sum_{i=1}^{N} \hat{V}^{+}_i(t_e) / N
549 for (Size i = 0; i < samples_; i++) {
550
551 // sequence is n independent standard normal random variables
552 vector<Real> sequence = rsg.nextSequence().value;
553
554 // w holds n correlated standard normal variables
555 Array w = sqrtCorr * Array(sequence.begin(), sequence.end());
556
557 // Update w to hold the sample value that we want i.e.
558 // F_{i,j}(t_e) / F_{i,j}(0) = exp{-var_j / 2} exp{stdDev_j w_{i,j}}
559 // where j = 1,..., n indexes the futures (i.e. date keys in the map)
560 // and i = 1,..., N indexes the number of Monte Carlo samples.
561 for (Size i = 0; i < w.size(); i++) {
562 w[i] = exp(stdDev[i] * w[i]) * expVar[i];
563 }
564
565 // Calculate float leg value on this iteration
566 Real sampleFloatLegValue = DotProduct(factor, w);
567
568 // Calculate the swap leg value on this iteration
569 Real sampleSwapValue = omega * (sampleFloatLegValue - valueFixedLeg);
570
571 // Update values dividing by samples each time to guard against blow up.
572 optionValue += max(sampleSwapValue, 0.0) / samples_;
573 swapValue += sampleSwapValue / samples_;
574 floatLegValue += sampleFloatLegValue / samples_;
575 }
576
577 // Populate the results remembering to multiply by P(0, t_e)
578 results_.value = discountExercise * optionValue;
579 results_.additionalResults["SwapNPV"] = discountExercise * swapValue;
580 results_.additionalResults["FixedLegNPV"] = discountExercise * valueFixedLeg;
581 results_.additionalResults["FloatingLegNPV"] = discountExercise * floatLegValue;
582}
583
584Real CommoditySwaptionMonteCarloEngine::spotFloatLegFactor(Size idxFloat, Real discountExercise) const {
585
586 // Different float leg valuation depending on whether leg is averaging or not.
587 Real floatLegValue = 0.0;
588 if (averaging(idxFloat)) {
589 for (const auto& cf : arguments_.legs[idxFloat]) {
590 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf);
591 QL_REQUIRE(ccf, "spotSwapValue: expected a CommodityIndexedAverageCashFlow");
592 Real disc = discountCurve_->discount(ccf->date());
593 floatLegValue += disc * ccf->amount();
594 }
595 } else {
596 for (const auto& cf : arguments_.legs[idxFloat]) {
597 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf);
598 QL_REQUIRE(ccf, "spotSwapValue: expected a CommodityIndexedCashFlow");
599 Real disc = discountCurve_->discount(ccf->date());
600 floatLegValue += disc * ccf->amount();
601 }
602 }
603
604 // Divide floating leg value by discount to swaption expiry
605 floatLegValue /= discountExercise;
606
607 // Return the swap value at the expiry date t_e
608 return floatLegValue;
609}
610
611void CommoditySwaptionMonteCarloEngine::futureFloatLegFactors(Size idxFloat, Real discountExercise,
612 const vector<Date>& expiries, Matrix& floatLegFactors,
613 Array& discounts, Array& amounts) const {
614
615 Size numCfs = arguments_.legs[idxFloat].size();
616
617 QL_REQUIRE(numCfs == floatLegFactors.rows(),
618 "Expected number of rows in floatLegFactors to equal number of cashflows");
619 QL_REQUIRE(numCfs == discounts.size(), "Expected size of discounts to equal number of cashflows");
620 QL_REQUIRE(numCfs == amounts.size(), "Expected size of amounts to equal number of cashflows");
621 QL_REQUIRE(expiries.size() == floatLegFactors.columns(),
622 "Expected number of columns in floatLegFactors to equal number of expiries");
623
624 // Different float leg valuation depending on whether leg is averaging or not.
625 if (averaging(idxFloat)) {
626 for (Size i = 0; i < numCfs; i++) {
627 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(arguments_.legs[idxFloat][i]);
628 QL_REQUIRE(ccf, "futureFloatLegFactors: expected a CommodityIndexedAverageCashFlow");
629 Size numObs = ccf->indices().size();
630 for (const auto& p : ccf->indices()) {
631 Date expiry = p.second->expiryDate();
632 auto it = find(expiries.begin(), expiries.end(), expiry);
633 QL_REQUIRE(it != expiries.end(), "futureFloatLegFactors: expected to find expiry in expiries vector");
634 auto idx = distance(expiries.begin(), it);
635 Real fxRate{1.};
636 if (ccf->fxIndex())
637 fxRate = ccf->fxIndex()->fixing(expiry);
638 floatLegFactors[i][idx] += fxRate * p.second->fixing(expiry) / numObs;
639 }
640 discounts[i] = discountCurve_->discount(ccf->date());
641 amounts[i] = ccf->periodQuantity();
642 }
643 } else {
644 for (Size i = 0; i < numCfs; i++) {
645 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(arguments_.legs[idxFloat][i]);
646 QL_REQUIRE(ccf, "futureFloatLegFactors: expected a CommodityIndexedCashFlow");
647
648 auto it = find(expiries.begin(), expiries.end(), ccf->index()->expiryDate());
649 QL_REQUIRE(it != expiries.end(), "futureFloatLegFactors: expected to find expiry in expiries vector");
650 auto idx = distance(expiries.begin(), it);
651 floatLegFactors[i][idx] = 1.0;
652 discounts[i] = discountCurve_->discount(ccf->date());
653 amounts[i] = ccf->amount();
654 }
655 }
656
657 // Divide discounts by discount to swaption expiry
658 for (Size i = 0; i < discounts.size(); i++) {
659 discounts[i] /= discountExercise;
660 }
661}
662
663map<Date, Real> CommoditySwaptionMonteCarloEngine::futureExpiries(Size idxFloat, Matrix& outSqrtCorr,
664 Real strike) const {
665
666 // Will hold the result
667 map<Date, Real> result;
668
669 // Populate the unique future expiry dates referenced on the floating leg
670 if (averaging(idxFloat)) {
671 for (const auto& cf : arguments_.legs[idxFloat]) {
672 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedAverageCashFlow>(cf);
673 QL_REQUIRE(ccf, "futureExpiries: expected a CommodityIndexedAverageCashFlow");
674 QL_REQUIRE(ccf->useFuturePrice(), "futureExpiries: expected the cashflow to reference a future price");
675 for (const auto& p : ccf->indices()) {
676 result[p.second->expiryDate()] = 0.0;
677 }
678 }
679 } else {
680 for (const auto& cf : arguments_.legs[idxFloat]) {
681 auto ccf = QuantLib::ext::dynamic_pointer_cast<CommodityIndexedCashFlow>(cf);
682 QL_REQUIRE(ccf, "futureExpiries: expected a CommodityIndexedCashFlow");
683 QL_REQUIRE(ccf->useFuturePrice(), "futureExpiries: expected the cashflow to reference a future price");
684 result[ccf->index()->expiryDate()] = 0.0;
685 }
686 }
687
688 // Populate the map values i.e. the instantaneous volatility associated with the future contract whose expiry date
689 // is the map key. Here, we make the simplifying assumption that the volatility can be read from the volatility
690 // term structure at the future contract's expiry date. In most cases, if the volatility term structure is built
691 // from options on futures, the option contract expiry will be a number of days before the future contract expiry
692 // and we should really read off the term structure at that date. Also populate a temp vector containing the key
693 // dates for use in the loop below where we populate the sqrt correlation matrix.
694 vector<Date> expiryDates;
695 for (auto& p : result) {
696 p.second = volStructure_->blackVol(p.first, strike);
697 expiryDates.push_back(p.first);
698 }
699
700 // Populate the outSqrtCorr matrix
701 outSqrtCorr = Matrix(expiryDates.size(), expiryDates.size(), 1.0);
702 for (Size i = 0; i < expiryDates.size(); i++) {
703 for (Size j = 0; j < i; j++) {
704 outSqrtCorr[i][j] = outSqrtCorr[j][i] = rho(expiryDates[i], expiryDates[j]);
705 }
706 }
707 outSqrtCorr = pseudoSqrt(outSqrtCorr, SalvagingAlgorithm::None);
708
709 return result;
710}
711
712} // namespace QuantExt
const Instrument::results * results_
Definition: cdsoption.cpp:81
Cash flow dependent on a single commodity spot price or futures settlement price on a given pricing d...
Handle< QuantLib::BlackVolTermStructure > volStructure_
QuantLib::Real rho(const QuantLib::Date &ed_1, const QuantLib::Date &ed_2) const
CommoditySwaptionBaseEngine(const Handle< YieldTermStructure > &discountCurve, const Handle< QuantLib::BlackVolTermStructure > &vol, Real beta=0.0)
QuantLib::Real fixedLegValue(QuantLib::Size fixedLegIndex) const
Give back the fixed leg price at the swaption expiry time.
bool averaging(QuantLib::Size floatLegIndex) const
QuantLib::Real strike(QuantLib::Size fixedLegIndex) const
QuantLib::Real expASquared(QuantLib::Size floatLegIndex, QuantLib::Real strike, QuantLib::Real normFactor) const
QuantLib::Real expA(QuantLib::Size floatLegIndex, QuantLib::Real normFactor) const
QuantLib::Real crossTerms(const QuantLib::ext::shared_ptr< QuantLib::CashFlow > &cf_1, const QuantLib::ext::shared_ptr< QuantLib::CashFlow > &cf_2, bool isAveraging, QuantLib::Real strike, QuantLib::Real normFactor) const
QuantLib::Real maxQuantity(QuantLib::Size floatLegIndex) const
void calculateSpot(QuantLib::Size idxFixed, QuantLib::Size idxFloat, QuantLib::Real strike) const
Calculations when underlying swap references a commodity spot price.
void futureFloatLegFactors(QuantLib::Size idxFloat, QuantLib::Real discountExercise, const std::vector< QuantLib::Date > &expiries, QuantLib::Matrix &floatLegFactors, QuantLib::Array &discounts, QuantLib::Array &amounts) const
void calculateFuture(QuantLib::Size idxFixed, QuantLib::Size idxFloat, QuantLib::Real strike) const
Calculations when underlying swap references a commodity spot price.
std::map< QuantLib::Date, QuantLib::Real > futureExpiries(QuantLib::Size idxFloat, QuantLib::Matrix &outSqrtCorr, QuantLib::Real strike) const
QuantLib::Real spotFloatLegFactor(QuantLib::Size idxFloat, QuantLib::Real discountExercise) const
Cash flow dependent on the average commodity spot price or future's settlement price over a period....
Cash flow dependent on a single commodity spot price or future's settlement price.
commodity swaption engine
RandomVariable sqrt(RandomVariable x)
CompiledFormula exp(CompiledFormula x)
CompiledFormula max(CompiledFormula x, const CompiledFormula &y)
CompiledFormula log(CompiledFormula x)
Swap::arguments * arguments_