QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
markovfunctional.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2013, 2018 Peter Caspers
5
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8
9 QuantLib is free software: you can redistribute it and/or modify it
10 under the terms of the QuantLib license. You should have received a
11 copy of the license along with this program; if not, please email
12 <quantlib-dev@lists.sf.net>. The license is also available online at
13 <http://quantlib.org/license.shtml>.
14
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the license for more details.
18*/
19
20#include <ql/math/integrals/gaussianquadratures.hpp>
21#include <ql/math/interpolations/cubicinterpolation.hpp>
22#include <ql/math/solvers1d/brent.hpp>
23#include <ql/models/shortrate/onefactormodels/markovfunctional.hpp>
24#include <ql/termstructures/volatility/atmadjustedsmilesection.hpp>
25#include <ql/termstructures/volatility/atmsmilesection.hpp>
26#include <ql/termstructures/volatility/kahalesmilesection.hpp>
27#include <ql/termstructures/volatility/sabrinterpolatedsmilesection.hpp>
28#include <ql/termstructures/volatility/smilesection.hpp>
29#include <ql/termstructures/volatility/smilesectionutils.hpp>
30#include <utility>
31
32namespace QuantLib {
33
35 const Real reversion,
36 std::vector<Date> volstepdates,
37 std::vector<Real> volatilities,
38 const Handle<SwaptionVolatilityStructure>& swaptionVol,
39 const std::vector<Date>& swaptionExpiries,
40 const std::vector<Period>& swaptionTenors,
41 const ext::shared_ptr<SwapIndex>& swapIndexBase,
43 : Gaussian1dModel(termStructure), CalibratedModel(1), modelSettings_(std::move(modelSettings)),
44 capletCalibrated_(false), reversion_(ConstantParameter(reversion, NoConstraint())),
45 sigma_(arguments_[0]), volstepdates_(std::move(volstepdates)),
46 volatilities_(std::move(volatilities)), swaptionVol_(swaptionVol),
47 swaptionExpiries_(swaptionExpiries), swaptionTenors_(swaptionTenors),
48 swapIndexBase_(swapIndexBase), iborIndex_(swapIndexBase->iborIndex()) {
49
50 QL_REQUIRE(swaptionExpiries.size() == swaptionTenors.size(),
51 "number of swaption expiries ("
52 << swaptionExpiries.size()
53 << ") is differnt from number of swaption tenors ("
54 << swaptionTenors.size() << ")");
55 QL_REQUIRE(!swaptionExpiries.empty(),
56 "need at least one swaption expiry to calibrate numeraire");
57 QL_REQUIRE(!termStructure.empty(),
58 "yield term structure handle is empty");
59 QL_REQUIRE(!swaptionVol.empty(),
60 "swaption volatility structure is empty");
62 initialize();
63 }
64
66 const Real reversion,
67 std::vector<Date> volstepdates,
68 std::vector<Real> volatilities,
70 const std::vector<Date>& capletExpiries,
71 ext::shared_ptr<IborIndex> iborIndex,
73 : Gaussian1dModel(termStructure), CalibratedModel(1), modelSettings_(std::move(modelSettings)),
74 capletCalibrated_(true), reversion_(ConstantParameter(reversion, NoConstraint())),
75 sigma_(arguments_[0]), volstepdates_(std::move(volstepdates)),
76 volatilities_(std::move(volatilities)), capletVol_(capletVol),
77 capletExpiries_(capletExpiries), iborIndex_(std::move(iborIndex)) {
78
79 QL_REQUIRE(!capletExpiries.empty(),
80 "need at least one caplet expiry to calibrate numeraire");
81 QL_REQUIRE(!termStructure.empty(),
82 "yield term structure handle is empty");
83 QL_REQUIRE(!capletVol.empty(), "caplet volatility structure is empty");
85 initialize();
86 }
87
89 QL_MFMESSAGE(modelOutputs_,"updating times");
92 }
93
95 volsteptimes_.clear();
96 int j = 0;
97 for (auto i = volstepdates_.begin(); i != volstepdates_.end(); ++i, ++j) {
98 volsteptimes_.push_back(termStructure()->timeFromReference(*i));
100 if (j == 0)
101 QL_REQUIRE(volsteptimes_[0] > 0.0,
102 "volsteptimes must be positive (" << volsteptimes_[0]
103 << ")");
104 else
105 QL_REQUIRE(volsteptimes_[j] > volsteptimes_[j - 1],
106 "volsteptimes must be strictly increasing ("
107 << volsteptimes_[j - 1] << "@" << (j - 1) << ", "
108 << volsteptimes_[j] << "@" << j << ")");
109 }
110 }
111
113 numeraireTime_ = termStructure()->timeFromReference(numeraireDate_);
114 times_.clear();
115 times_.push_back(0.0);
116 modelOutputs_.expiries_.clear();
117 modelOutputs_.tenors_.clear();
118 for (auto& calibrationPoint : calibrationPoints_) {
119 times_.push_back(termStructure()->timeFromReference(calibrationPoint.first));
120 modelOutputs_.expiries_.push_back(calibrationPoint.first);
121 modelOutputs_.tenors_.push_back(calibrationPoint.second.tenor_);
122 }
123 times_.push_back(numeraireTime_);
124 QL_REQUIRE(volatilities_.size() == volsteptimes_.size() + 1,
125 "there must be n+1 volatilities ("
126 << volatilities_.size()
127 << ") for n volatility step times ("
128 << volsteptimes_.size() << ")");
129 }
130
131
133
134 QL_MFMESSAGE(modelOutputs_, "initializing");
135 modelOutputs_.dirty_ = true;
136
138
139 GaussHermiteIntegration gaussHermite(
141 normalIntegralX_ = gaussHermite.x();
142 normalIntegralW_ = gaussHermite.weights();
143 for (Size i = 0; i < normalIntegralX_.size(); i++) {
144 normalIntegralW_[i] *=
145 std::exp(-normalIntegralX_[i] * normalIntegralX_[i]) * M_1_SQRTPI;
146 normalIntegralX_[i] *= M_SQRT2;
147 }
148
150
151 updateTimes1();
152
153 if (capletCalibrated_) {
154 for (auto capletExpirie : capletExpiries_) {
155 makeCapletCalibrationPoint(capletExpirie);
156 }
157 } else {
158 std::vector<Date>::const_iterator i;
159 std::vector<Period>::const_iterator j;
160 for (i = swaptionExpiries_.begin(), j = swaptionTenors_.begin();
161 i != swaptionExpiries_.end(); ++i, ++j) {
163 }
164 }
165
166 bool done;
168 do {
169 Date numeraireKnown = numeraireDate_;
170 done = true;
171 for (auto i = calibrationPoints_.rbegin(); i != calibrationPoints_.rend() && done;
172 ++i) {
173 if (i->second.paymentDates_.back() > numeraireDate_) {
174 numeraireDate_ = i->second.paymentDates_.back();
175 numeraireKnown = i->second.paymentDates_.back();
176 done = i == calibrationPoints_.rbegin();
177 }
178 for (auto j = i->second.paymentDates_.rbegin();
179 j != i->second.paymentDates_.rend() && done; ++j) {
180 if (*j < numeraireKnown) {
181 if (capletCalibrated_) {
183 done = false;
184 break;
185 } else {
186 Size months = std::max(
187 1, static_cast<Integer>(((numeraireKnown - *j) / 365.25) * 12.0));
189 ->maturityDate() < numeraireKnown)
190 ++months;
192 done = false;
193 break;
194 }
195 }
196 }
197 if (done) {
198 numeraireKnown = i->first;
199 }
200 }
201 } while (!done);
202
203 updateTimes2();
204
205 sigma_ =
207 for (Size i = 0; i < sigma_.size(); i++) {
209 }
210
211 stateProcess_ = ext::make_shared<MfStateProcess>(
213
215
216 discreteNumeraire_ = ext::make_shared<Matrix>(
217 times_.size(), 2 * modelSettings_.yGridPoints_ + 1, 1.0);
218 for (Size i = 0; i < times_.size(); i++) {
219 ext::shared_ptr<Interpolation> numInt(new CubicInterpolation(
220 y_.begin(), y_.end(), discreteNumeraire_->row_begin(i),
223 numInt->enableExtrapolation();
224 numeraire_.push_back(numInt);
225 }
226
228 if (!swaptionVol_.empty())
230 if (!capletVol_.empty())
232 }
233
235 const Period &tenor) {
236
237 QL_REQUIRE(calibrationPoints_.count(expiry) == 0,
238 "swaption expiry ("
239 << expiry
240 << ") occurs more than once in calibration set");
241
243 p.isCaplet_ = false;
244 p.tenor_ = tenor;
245
246 ext::shared_ptr<VanillaSwap> underlying = underlyingSwap(swapIndexBase_, expiry, tenor);
247
248 Schedule sched = underlying->fixedSchedule();
249 const Calendar& cal = sched.calendar();
250 BusinessDayConvention bdc = underlying->paymentConvention();
251
252 for (unsigned int k = 1; k < sched.size(); k++) {
253 p.yearFractions_.push_back(
254 swapIndexBase_->dayCounter().yearFraction(
255 k == 1 ? expiry : sched.date(k - 1), sched.date(k)));
256 p.paymentDates_.push_back(cal.adjust(sched.date(k), bdc));
257 }
258 calibrationPoints_[expiry] = p;
259 }
260
262
263 QL_REQUIRE(
264 calibrationPoints_.count(expiry) == 0,
265 "caplet expiry (" << expiry
266 << ") occurs more than once in calibration set");
267
269 p.isCaplet_ = true;
270 // p.expiry_ = expiry;
271 p.tenor_ = iborIndex_->tenor();
272 Date valueDate = iborIndex_->valueDate(expiry);
273 Date endDate = iborIndex_->fixingCalendar().advance(
274 valueDate, iborIndex_->tenor(), iborIndex_->businessDayConvention(),
275 iborIndex_->endOfMonth());
276 // FIXME Here we should use a calculation date calendar ?
277 p.paymentDates_.push_back(endDate);
278 p.yearFractions_.push_back(
279 iborIndex_->dayCounter().yearFraction(expiry, endDate));
280 // adjust the first period to start on expiry
281 calibrationPoints_[expiry] = p;
282 }
283
285
286 QL_MFMESSAGE(modelOutputs_, "updating smiles");
287 modelOutputs_.dirty_ = true;
288
289 arbitrageIndices_.clear();
290
291 Size pointIndex = 0;
292
293 for (auto i = calibrationPoints_.rbegin(); i != calibrationPoints_.rend(); ++i) {
294
295 ext::shared_ptr<SmileSection> smileSection;
296 if (i->second.isCaplet_) {
297 i->second.annuity_ =
298 i->second.yearFractions_[0] *
299 termStructure()->discount(i->second.paymentDates_[0], true);
300 i->second.atm_ = (termStructure()->discount(i->first, true) -
301 termStructure()->discount(
302 i->second.paymentDates_[0], true)) /
303 i->second.annuity_;
304 smileSection = capletVol_->smileSection(i->first, true);
305 } else {
306 Real annuity = 0.0;
307 for (unsigned int k = 0; k < i->second.paymentDates_.size();
308 k++) {
309 annuity += i->second.yearFractions_[k] *
310 termStructure()->discount(
311 i->second.paymentDates_[k], true);
312 }
313 i->second.annuity_ = annuity;
314 i->second.atm_ = (termStructure()->discount(i->first, true) -
315 termStructure()->discount(
316 i->second.paymentDates_.back(), true)) /
317 annuity;
318 smileSection = swaptionVol_->smileSection(
319 i->first, i->second.tenor_, true);
320 }
321
322 i->second.rawSmileSection_ = ext::shared_ptr<SmileSection>(
323 new AtmSmileSection(smileSection, i->second.atm_));
324
325 int forcedLeftIndex = -1;
326 int forcedRightIndex = QL_MAX_INTEGER;
327 if(forcedArbitrageIndices_.size() > pointIndex) {
328 forcedLeftIndex = forcedArbitrageIndices_[pointIndex].first;
329 forcedRightIndex = forcedArbitrageIndices_[pointIndex].second;
330 }
331
333
334 i->second.smileSection_ = ext::make_shared<KahaleSmileSection>(
335
336 i->second.rawSmileSection_, i->second.atm_,
345 forcedLeftIndex, forcedRightIndex);
346
347 arbitrageIndices_.push_back(
348 ext::dynamic_pointer_cast<KahaleSmileSection>(
349 i->second.smileSection_)->coreIndices());
350
351 } else {
352
354
355 SmileSectionUtils ssutils(
356 *i->second.rawSmileSection_,
358 std::vector<Real> k = ssutils.strikeGrid();
359 k.erase(k.begin()); // the first strike is zero which we do
360 // not want in the sabr calibration
361 QL_REQUIRE(i->second.rawSmileSection_->volatilityType() ==
363 "MarkovFunctional: SABR calibration to normal "
364 "input volatilities is not supported");
365 QL_REQUIRE(
366 k.size() >= 4,
367 "for sabr calibration at least 4 points are needed (is "
368 << k.size() << ")");
369 std::vector<Real> v;
370 v.reserve(k.size());
371 for (Real j : k) {
372 v.push_back(i->second.rawSmileSection_->volatility(j));
373 }
374
375 // TODO should we fix beta to avoid numerical instabilities
376 // during calibration ?
377 ext::shared_ptr<SabrInterpolatedSmileSection> sabrSection(
379 i->first, i->second.atm_, k, false,
380 i->second.rawSmileSection_->volatility(
381 i->second.atm_),
382 v, 0.03, 0.80, 0.50, 0.00, false, false, false,
383 false, true, ext::shared_ptr<EndCriteria>(),
384 ext::shared_ptr<OptimizationMethod>(),
386 i->second.rawSmileSection_->shift()));
387
388 // we make the sabr section arbitrage free by superimposing
389 // a kahalesection
390
391 i->second.smileSection_ = ext::make_shared<
393 sabrSection, i->second.atm_, false,
400 forcedLeftIndex, forcedRightIndex);
401
402 arbitrageIndices_.push_back(
403 ext::dynamic_pointer_cast<KahaleSmileSection>(
404 i->second.smileSection_)->coreIndices());
405
407
408 // Custom smile section is af by assumption
409 i->second.smileSection_ =
411 i->second.rawSmileSection_, i->second.atm_);
412 arbitrageIndices_.emplace_back(Null<Size>(), Null<Size>());
413 } else { // no smile pretreatment
414
415 i->second.smileSection_ = i->second.rawSmileSection_;
416 }
417 }
418
419 // custom smile will take care of this itself
421 i->second.minRateDigital_ =
422 i->second.smileSection_->digitalOptionPrice(
424 i->second.smileSection_->shift(),
425 Option::Call, i->second.annuity_,
427 i->second.maxRateDigital_ =
428 i->second.smileSection_->digitalOptionPrice(
430 i->second.smileSection_->shift(),
431 Option::Call, i->second.annuity_,
433 }
434
435 ++pointIndex;
436 }
437 }
438
440
441 QL_MFMESSAGE(modelOutputs_, "updating numeraire tabulation");
442 modelOutputs_.dirty_ = true;
443
446
447 int idx = times_.size() - 2;
448
449 for (auto i = calibrationPoints_.rbegin(); i != calibrationPoints_.rend(); ++i, --idx) {
450
451 ext::shared_ptr<CustomSmileSection> mfSec;
453 mfSec = ext::dynamic_pointer_cast<CustomSmileSection>(
454 i->second.smileSection_);
455 QL_REQUIRE(mfSec,
456 "no CustomSmileSection given, this is unexpected...");
457 }
458
459 Array discreteDeflatedAnnuities(y_.size(), 0.0);
460 Array deflatedFinalPayments;
461
462 Real numeraire0 = termStructure()->discount(numeraireTime_, true);
463 Real normalization =
464 termStructure()->discount(times_[idx], true) / numeraire0;
465
466 for (unsigned int k = 0; k < i->second.paymentDates_.size(); k++) {
467 deflatedFinalPayments =
468 deflatedZerobondArray(termStructure()->timeFromReference(
469 i->second.paymentDates_[k]),
470 times_[idx], y_);
471 discreteDeflatedAnnuities +=
472 deflatedFinalPayments * i->second.yearFractions_[k];
473 }
474
475 CubicInterpolation deflatedAnnuities(
476 y_.begin(), y_.end(), discreteDeflatedAnnuities.begin(),
479 deflatedAnnuities.enableExtrapolation();
480
481 Real digitalsCorrectionFactor = 1.0;
484 digitalsCorrectionFactor);
485
486 Real digital = 0.0, swapRate, swapRate0;
487
488 for (int c = 0;
489 c == 0 ||
491 c++) {
492
493 if (c == 1) {
494 digitalsCorrectionFactor = i->second.annuity_ / digital;
496 digitalsCorrectionFactor;
497 }
498
499 digital = 0.0;
500 swapRate0 =
501 modelSettings_.upperRateBound_ / 2.0; // initial guess
502 for (int j = y_.size() - 1; j >= 0; j--) {
503
504 Real integral = 0.0;
505
506 if (j == (int)(y_.size() - 1)) {
512 0.0, 0.0, 0.0, 0.0,
513 discreteDeflatedAnnuities[j - 1], y_[j - 1],
514 y_[j], 100.0);
515 } else {
516 Real ca =
517 deflatedAnnuities.aCoefficients()[j - 1];
518 Real cb =
519 deflatedAnnuities.bCoefficients()[j - 1];
520 Real cc =
521 deflatedAnnuities.cCoefficients()[j - 1];
523 0.0, cc, cb, ca,
524 discreteDeflatedAnnuities[j - 1], y_[j - 1],
525 y_[j], 100.0);
526 }
527 }
528 } else {
529 Real ca = deflatedAnnuities.aCoefficients()[j];
530 Real cb = deflatedAnnuities.bCoefficients()[j];
531 Real cc = deflatedAnnuities.cCoefficients()[j];
533 0.0, cc, cb, ca, discreteDeflatedAnnuities[j],
534 y_[j], y_[j], y_[j + 1]);
535 }
536
537 if (integral < 0) {
538 QL_MFMESSAGE(modelOutputs_,
539 "WARNING: integral for digitalPrice is "
540 "negative for j="
541 << j << " (" << integral
542 << ") --- reset it to zero.");
543 integral = 0.0;
544 }
545
546 digital += integral * numeraire0 * digitalsCorrectionFactor;
547
548 bool check = true;
550 swapRate = mfSec->inverseDigitalCall(
551 digital, i->second.annuity_);
552 } else if (digital >= i->second.minRateDigital_) {
554 i->second.rawSmileSection_->shift();
555 check = false;
556 } else if (digital <= i->second.maxRateDigital_) {
558 check = false;
559 } else {
561 i->first, i->second, digital, swapRate0,
562 i->second.rawSmileSection_->shift());
563 }
564 if (check && j < (int)y_.size() - 1 &&
565 swapRate > swapRate0) {
566 QL_MFMESSAGE(
568 "WARNING: swap rate is decreasing in y for "
569 "t=" << times_[idx]
570 << ", j=" << j << " (y, swap rate) is ("
571 << y_[j] << "," << swapRate << ") but for j="
572 << j + 1 << " it is (" << y_[j + 1] << ","
573 << swapRate0 << ") --- reset rate to "
574 << swapRate0 << " in node j=" << j);
575 swapRate = swapRate0;
576 }
577 swapRate0 = swapRate;
579 1.0 / std::max(swapRate * discreteDeflatedAnnuities[j] +
580 deflatedFinalPayments[j], 1E-6);
581 (*discreteNumeraire_)[idx][j] = numeraire * normalization;
582 }
583 }
584
586 numeraire_[idx]->update();
587 Real modelDeflatedZerobond = deflatedZerobond(times_[idx], 0.0);
588 Real marketDeflatedZerobond =
589 termStructure()->discount(times_[idx], true) /
590 termStructure()->discount(numeraireTime_, true);
591 for (int j = y_.size() - 1; j >= 0; j--) {
592 (*discreteNumeraire_)[idx][j] *=
593 modelDeflatedZerobond / marketDeflatedZerobond;
594 }
597 modelDeflatedZerobond / marketDeflatedZerobond);
598 } else {
600 modelOutputs_.adjustmentFactors_.begin(), 1.0);
601 }
602
603 numeraire_[idx]->update();
604 }
605 }
606
609
610 if (modelOutputs_.dirty_) {
611
612 calculate();
613
614 // yield term structure
617 for (Size i = 1; i < times_.size() - 1; i++) {
621 // we need to put a small positive time here since the zerobond
622 // implementation optimizes the case t=0.0 then using the
623 // initial yts
625 -std::log(zerobond(times_[i], 1.0E-10)) / times_[i]);
626 }
627
628 // volatility surface
637
638 for (auto& calibrationPoint : calibrationPoints_) {
639 modelOutputs_.atm_.push_back(calibrationPoint.second.atm_);
640 modelOutputs_.annuity_.push_back(calibrationPoint.second.annuity_);
641 ext::shared_ptr<SmileSection> sec = calibrationPoint.second.smileSection_;
642 ext::shared_ptr<SmileSection> rawSec = calibrationPoint.second.rawSmileSection_;
644 calibrationPoint.second.atm_);
645 Real shift = sec->shift();
646 std::vector<Real> money = ssutils.moneyGrid();
647 std::vector<Real> strikes, marketCall, marketPut, modelCall,
648 modelPut, marketVega, marketRawCall, marketRawPut;
649 for (Size j = 0; j < money.size(); j++) {
650 strikes.push_back(sec->volatilityType() == Normal ?
651 Real(calibrationPoint.second.atm_ + money[j]) :
652 money[j] * (calibrationPoint.second.atm_ + shift) -
653 shift);
654 try {
655 marketRawCall.push_back(rawSec->optionPrice(
656 strikes[j], Option::Call, calibrationPoint.second.annuity_));
657 marketRawPut.push_back(rawSec->optionPrice(
658 strikes[j], Option::Put, calibrationPoint.second.annuity_));
659 }
660 catch (Error&) {
661 // the smile section might not be able to output an
662 // option price because it has no atm level
663 marketRawCall.push_back(0.0);
664 marketRawPut.push_back(0.0);
665 }
666 marketCall.push_back(sec->optionPrice(strikes[j], Option::Call,
667 calibrationPoint.second.annuity_));
668 marketPut.push_back(sec->optionPrice(strikes[j], Option::Put,
669 calibrationPoint.second.annuity_));
670 modelCall.push_back(
671 calibrationPoint.second.isCaplet_ ?
672 capletPriceInternal(Option::Call, calibrationPoint.first, strikes[j],
673 Null<Date>(), 0.0, true) :
674 swaptionPriceInternal(Option::Call, calibrationPoint.first,
675 calibrationPoint.second.tenor_, strikes[j],
676 Null<Date>(), 0.0, true));
677 modelPut.push_back(
678 calibrationPoint.second.isCaplet_ ?
679 capletPriceInternal(Option::Put, calibrationPoint.first, strikes[j],
680 Null<Date>(), 0.0, true) :
681 swaptionPriceInternal(Option::Put, calibrationPoint.first,
682 calibrationPoint.second.tenor_, strikes[j],
683 Null<Date>(), 0.0, true));
684 marketVega.push_back(sec->vega(strikes[j], calibrationPoint.second.annuity_));
685 }
686 modelOutputs_.smileStrikes_.push_back(strikes);
687 modelOutputs_.marketCallPremium_.push_back(marketCall);
688 modelOutputs_.marketPutPremium_.push_back(marketPut);
689 modelOutputs_.modelCallPremium_.push_back(modelCall);
690 modelOutputs_.modelPutPremium_.push_back(modelPut);
691 modelOutputs_.marketVega_.push_back(marketVega);
692 modelOutputs_.marketRawCallPremium_.push_back(marketRawCall);
693 modelOutputs_.marketRawPutPremium_.push_back(marketRawPut);
694 }
695
696 modelOutputs_.dirty_ = false;
697 }
698
699 return modelOutputs_;
700 }
701
703
704 calculate();
705 Array res(y.size(), termStructure()->discount(numeraireTime_, true));
706 if (t < QL_EPSILON)
707 return res;
708
709 Real inverseNormalization =
710 termStructure()->discount(numeraireTime_, true) /
711 termStructure()->discount(t, true);
712
713 Time tz = std::min(t, times_.back());
714 Size i = std::min<Size>(
715 std::upper_bound(times_.begin(), times_.end() - 1, t) -
716 times_.begin(),
717 times_.size() - 1);
718
719 Real ta = times_[i - 1];
720 Real tb = times_[i];
721 Real dt = tb - ta;
722
723 for (Size j = 0; j < y.size(); j++) {
724 Real yv = y[j];
725 if (yv < y_.front())
726 yv = y_.front();
727 // FIXME flat extrapolation should be incoperated into interpolation
728 // object, see above
729 if (yv > y_.back())
730 yv = y_.back();
731 Real na = (*numeraire_[i - 1])(yv);
732 Real nb = (*numeraire_[i])(yv);
733 res[j] =
734 inverseNormalization / ((tz - ta) / nb + (tb - tz) / na) * dt;
735 // linear in reciprocal of normalized numeraire
736 }
737
738 return res;
739 }
740
741 Array MarkovFunctional::zerobondArray(const Time T, const Time t, const Array& y) const {
742
743 return deflatedZerobondArray(T, t, y) * numeraireArray(t, y);
744 }
745
746 Array MarkovFunctional::deflatedZerobondArray(const Time T, const Time t, const Array& y) const {
747
748 calculate();
749
750 Array result(y.size(), 0.0);
751
752 // Gauss Hermite
753
754 Real stdDev_0_t = stateProcess_->stdDeviation(0.0, 0.0, t);
755 // we use that the standard deviation is independent of $x$ here
756 Real stdDev_0_T = stateProcess_->stdDeviation(0.0, 0.0, T);
757 Real stdDev_t_T = stateProcess_->stdDeviation(t, 0.0, T - t);
758
759 for (Size j = 0; j < y.size(); j++) {
761 for (Size i = 0; i < modelSettings_.gaussHermitePoints_; i++) {
762 ya[i] = (y[j] * stdDev_0_t + stdDev_t_T * normalIntegralX_[i]) /
763 stdDev_0_T;
764 }
765 Array res = numeraireArray(T, ya);
766 for (Size i = 0; i < modelSettings_.gaussHermitePoints_; i++) {
767 result[j] += normalIntegralW_[i] / res[i];
768 }
769 }
770
771 return result;
772 }
773
775 const Time t, const Real y,
776 const Handle<YieldTermStructure> &yts) const {
777
778 if (t == 0)
779 return yts.empty()
780 ? this->termStructure()->discount(numeraireTime(), true)
781 : yts->discount(numeraireTime());
782
783 Array ya(1, y);
784 return numeraireArray(t, ya)[0] *
785 (yts.empty() ? Real(1.0)
786 : (yts->discount(numeraireTime()) /
787 yts->discount(t) * termStructure()->discount(t) /
788 termStructure()->discount(numeraireTime())));
789 }
790
791 Real
792 MarkovFunctional::zerobondImpl(const Time T, const Time t, const Real y,
793 const Handle<YieldTermStructure> &yts) const {
794
795 if (t == 0.0)
796 return yts.empty() ? this->termStructure()->discount(T, true)
797 : yts->discount(T, true);
798 Array ya(1, y);
799 return zerobondArray(T, t, ya)[0] *
800 (yts.empty() ? Real(1.0) : (yts->discount(T) / yts->discount(t) *
801 termStructure()->discount(t) /
802 termStructure()->discount(T)));
803 }
804
806 Real y) const {
807
808 Array ya(1, y);
809 return deflatedZerobondArray(T, t, ya)[0];
810 }
811
813 const CalibrationPoint &p,
814 const Real digitalPrice,
815 const Real guess,
816 const Real shift) const {
817
818 ZeroHelper z(this, expiry, p, digitalPrice);
819 Brent b;
820 Real solution = b.solve(
822 std::max(std::min(guess, modelSettings_.upperRateBound_ - 0.00001),
823 modelSettings_.lowerRateBound_ - shift + 0.00001),
825 return solution;
826 }
827
829 const CalibrationPoint &p,
830 const Option::Type &type,
831 const Real strike) const {
832
833 return p.smileSection_->digitalOptionPrice(strike, type, p.annuity_,
835 }
836
837 std::ostream &operator<<(std::ostream &out,
839 out << "Markov functional model trace output " << std::endl;
840 out << "Model settings" << std::endl;
841 out << "Grid points y : " << m.settings_.yGridPoints_
842 << std::endl;
843 out << "Std devs y : " << m.settings_.yStdDevs_ << std::endl;
844 out << "Lower rate bound : " << m.settings_.lowerRateBound_
845 << std::endl;
846 out << "Upper rate bound : " << m.settings_.upperRateBound_
847 << std::endl;
848 out << "Gauss Hermite points : " << m.settings_.gaussHermitePoints_
849 << std::endl;
850 out << "Digital gap : " << m.settings_.digitalGap_
851 << std::endl;
852 out << "Adjustments : "
854 "Digitals " :
855 "")
857 "Yts " :
858 "")
859 << ((m.settings_.adjustments_ &
861 "FlatPayoffExt " :
862 "")
863 << ((m.settings_.adjustments_ &
865 "NoPayoffExt " :
866 "")
868 "Kahale " :
869 "")
870 << ((m.settings_.adjustments_ &
872 "SmileExp " :
873 "")
875 0 ?
876 "KahaleInt " :
877 "")
878 << ((m.settings_.adjustments_ &
880 "SmileDelArb " :
881 "")
883 "Sabr" :
884 "")
885 << std::endl;
886 out << "Smile moneyness checkpoints: ";
887 for (Size i = 0; i < m.settings_.smileMoneynessCheckpoints_.size(); i++)
889 << (i < m.settings_.smileMoneynessCheckpoints_.size() - 1 ? ";"
890 : "");
891 out << std::endl;
892
893 QL_REQUIRE(!m.dirty_, "model outputs are dirty");
894
895 if (m.expiries_.empty())
896 return out; // no trace information was collected so no output
897 out << std::endl;
898 out << "Messages:" << std::endl;
899 for (const auto& message : m.messages_)
900 out << message << std::endl;
901 out << std::endl << std::setprecision(16);
902 out << "Yield termstructure fit:" << std::endl;
903 out << "expiry;tenor;atm;annuity;digitalAdj;ytsAdj;marketzerorate;"
904 "modelzerorate;diff(bp)" << std::endl;
905 for (Size i = 0; i < m.expiries_.size(); i++) {
906 out << m.expiries_[i] << ";" << m.tenors_[i] << ";" << m.atm_[i]
907 << ";" << m.annuity_[i] << ";"
908 << m.digitalsAdjustmentFactors_[i] << ";"
909 << m.adjustmentFactors_[i] << ";" << m.marketZerorate_[i] << ";"
910 << m.modelZerorate_[i] << ";"
911 << (m.marketZerorate_[i] - m.modelZerorate_[i]) * 10000.0
912 << std::endl;
913 }
914 out << std::endl;
915 out << "Volatility smile fit:" << std::endl;
916 for (Size i = 0; i < m.expiries_.size(); i++) {
917 std::ostringstream os;
918 os << m.expiries_[i] << "/" << m.tenors_[i];
919 std::string p = os.str();
920 out << "strike(" << p << ");marketCallRaw(" << p << ";marketCall("
921 << p << ");modelCall(" << p << ");marketPutRaw(" << p
922 << ");marketPut(" << p << ");modelPut(" << p << ");marketVega("
923 << p << ")" << (i < m.expiries_.size() - 1 ? ";" : "");
924 }
925 out << std::endl;
926 for (Size j = 0; j < m.smileStrikes_[0].size(); j++) {
927 for (Size i = 0; i < m.expiries_.size(); i++) {
928 out << m.smileStrikes_[i][j] << ";"
929 << m.marketRawCallPremium_[i][j] << ";"
930 << m.marketCallPremium_[i][j] << ";"
931 << m.modelCallPremium_[i][j] << ";"
932 << m.marketRawPutPremium_[i][j] << ";"
933 << m.marketPutPremium_[i][j] << ";"
934 << m.modelPutPremium_[i][j] << ";" << m.marketVega_[i][j]
935 << (i < m.expiries_.size() - 1 ? ";" : "");
936 }
937 out << std::endl;
938 }
939 return out;
940 }
941
943 const Date &fixing, const Date &referenceDate, const Real y,
944 const bool zeroFixingDays, ext::shared_ptr<IborIndex> iborIdx) const {
945
946 calculate();
947
948 if (!iborIdx)
949 iborIdx = iborIndex_;
950
951 Date valueDate = zeroFixingDays ? fixing : iborIdx->valueDate(fixing);
952 Date endDate = iborIdx->fixingCalendar().advance(
953 iborIdx->valueDate(fixing), iborIdx->tenor(),
954 iborIdx->businessDayConvention(),
955 iborIdx->endOfMonth()); // FIXME Here we should use the calculation
956 // date calendar ?
957 Real dcf = iborIdx->dayCounter().yearFraction(valueDate, endDate);
958
959 return (zerobond(valueDate, referenceDate, y) -
960 zerobond(endDate, referenceDate, y)) /
961 (dcf * zerobond(endDate, referenceDate, y));
962 }
963
964 Real
965 MarkovFunctional::swapRateInternal(const Date &fixing, const Period &tenor,
966 const Date &referenceDate, const Real y,
967 bool zeroFixingDays,
968 ext::shared_ptr<SwapIndex> swapIdx) const {
969
970 calculate();
971
972 if (!swapIdx)
973 swapIdx = swapIndexBase_;
974 QL_REQUIRE(swapIdx, "No swap index given");
975
976 ext::shared_ptr<VanillaSwap> underlying = underlyingSwap(swapIdx, fixing, tenor);
977
978 Schedule sched = underlying->fixedSchedule();
979 Real annuity = swapAnnuityInternal(fixing, tenor, referenceDate, y,
980 zeroFixingDays, swapIdx);
981 Rate atm =
982 (zerobond(zeroFixingDays ? fixing : sched.dates().front(),
983 referenceDate, y) -
984 zerobond(sched.calendar().adjust(sched.dates().back(),
985 underlying->paymentConvention()),
986 referenceDate, y)) /
987 annuity;
988 return atm;
989 }
990
992 const Date &fixing, const Period &tenor, const Date &referenceDate,
993 const Real y, const bool zeroFixingDays,
994 ext::shared_ptr<SwapIndex> swapIdx) const {
995
996 calculate();
997
998 if (!swapIdx)
999 swapIdx = swapIndexBase_;
1000 QL_REQUIRE(swapIdx, "No swap index given");
1001
1002 ext::shared_ptr<VanillaSwap> underlying = underlyingSwap(swapIdx, fixing, tenor);
1003
1004 Schedule sched = underlying->fixedSchedule();
1005
1006 Real annuity = 0.0;
1007 for (unsigned int j = 1; j < sched.size(); j++) {
1008 annuity +=
1009 zerobond(sched.calendar().adjust(
1010 sched.date(j), underlying->paymentConvention()),
1011 referenceDate, y) *
1012 swapIdx->dayCounter().yearFraction(
1013 j == 1 && zeroFixingDays ? fixing : sched.date(j - 1),
1014 sched.date(j));
1015 }
1016 return annuity;
1017 }
1018
1020 const Date& expiry,
1021 const Period& tenor,
1022 const Rate strike,
1023 const Date& referenceDate,
1024 const Real y,
1025 const bool zeroFixingDays,
1026 const ext::shared_ptr<SwapIndex>& swapIdx) const {
1027
1028 calculate();
1029
1030 Time fixingTime = termStructure()->timeFromReference(expiry);
1031 Time referenceTime =
1032 referenceDate == Null<Date>()
1033 ? 0.0
1034 : termStructure()->timeFromReference(referenceDate);
1035
1037 fixingTime, referenceTime, y);
1039 Array p(yg.size());
1040
1041 for (Size i = 0; i < yg.size(); i++) {
1042 Real annuity = swapAnnuityInternal(expiry, tenor, expiry, yg[i],
1043 zeroFixingDays, swapIdx);
1044 Rate atm = swapRateInternal(expiry, tenor, expiry, yg[i], zeroFixingDays,
1045 swapIdx);
1046 p[i] = annuity * std::max((type == Option::Call ? 1.0 : -1.0) *
1047 (atm - strike),
1048 0.0) /
1049 numeraire(fixingTime, yg[i]);
1050 }
1051
1052 CubicInterpolation payoff(z.begin(), z.end(), p.begin(),
1056
1057 Real price = 0.0;
1058 for (Size i = 0; i < z.size() - 1; i++) {
1060 0.0, payoff.cCoefficients()[i], payoff.bCoefficients()[i],
1061 payoff.aCoefficients()[i], p[i], z[i], z[i], z[i + 1]);
1062 }
1068 0.0, 0.0, 0.0, 0.0, p[z.size() - 2], z[z.size() - 2],
1069 z[z.size() - 1], 100.0);
1071 0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0, z[0]);
1072 } else {
1073 if (type == Option::Call)
1075 0.0, payoff.cCoefficients()[z.size() - 2],
1076 payoff.bCoefficients()[z.size() - 2],
1077 payoff.aCoefficients()[z.size() - 2], p[z.size() - 2],
1078 z[z.size() - 2], z[z.size() - 1], 100.0);
1079 if (type == Option::Put)
1081 0.0, payoff.cCoefficients()[0],
1082 payoff.bCoefficients()[0], payoff.aCoefficients()[0],
1083 p[0], z[0], -100.0, z[0]);
1084 }
1085 }
1086
1087 return numeraire(referenceTime, y) * price;
1088 }
1089
1091 const Option::Type &type, const Date &expiry, const Rate strike,
1092 const Date &referenceDate, const Real y, const bool zeroFixingDays,
1093 ext::shared_ptr<IborIndex> iborIdx) const {
1094
1095 calculate();
1096
1097 if (!iborIdx)
1098 iborIdx = iborIndex_;
1099
1100 Time fixingTime = termStructure()->timeFromReference(expiry);
1101 Time referenceTime =
1102 referenceDate == Null<Date>()
1103 ? 0.0
1104 : termStructure()->timeFromReference(referenceDate);
1105
1107 fixingTime, referenceTime, y);
1109 Array p(yg.size());
1110
1111 Date valueDate = iborIdx->valueDate(expiry);
1112 Date endDate = iborIdx->fixingCalendar().advance(
1113 valueDate, iborIdx->tenor(), iborIdx->businessDayConvention(),
1114 iborIdx->endOfMonth()); // FIXME Here we should use the calculation
1115 // date calendar ?
1116 Real dcf = iborIdx->dayCounter().yearFraction(
1117 zeroFixingDays ? expiry : valueDate, endDate);
1118
1119 for (Size i = 0; i < yg.size(); i++) {
1120 Real annuity = zerobond(endDate, expiry, yg[i]) * dcf;
1121 Rate atm =
1122 forwardRateInternal(expiry, expiry, yg[i], zeroFixingDays, iborIdx);
1123 p[i] = annuity * std::max((type == Option::Call ? 1.0 : -1.0) *
1124 (atm - strike),
1125 0.0) /
1126 numeraire(fixingTime, yg[i]);
1127 }
1128
1129 CubicInterpolation payoff(z.begin(), z.end(), p.begin(),
1133
1134 Real price = 0.0;
1135 for (Size i = 0; i < z.size() - 1; i++) {
1137 0.0, payoff.cCoefficients()[i], payoff.bCoefficients()[i],
1138 payoff.aCoefficients()[i], p[i], z[i], z[i], z[i + 1]);
1139 }
1145 0.0, 0.0, 0.0, 0.0, p[z.size() - 2], z[z.size() - 2],
1146 z[z.size() - 1], 100.0);
1148 0.0, 0.0, 0.0, 0.0, p[0], z[0], -100.0, z[0]);
1149 } else {
1150 if (type == Option::Call)
1152 0.0, payoff.cCoefficients()[z.size() - 2],
1153 payoff.bCoefficients()[z.size() - 2],
1154 payoff.aCoefficients()[z.size() - 2], p[z.size() - 2],
1155 z[z.size() - 2], z[z.size() - 1], 100.0);
1156 if (type == Option::Put)
1158 0.0, payoff.cCoefficients()[0],
1159 payoff.bCoefficients()[0], payoff.aCoefficients()[0],
1160 p[0], z[0], -100.0, z[0]);
1161 }
1162 }
1163
1164 return numeraire(referenceTime, y) * price;
1165 }
1166}
Actual/365 (Fixed) day count convention.
1-D array used in linear algebra.
Definition: array.hpp:52
const_iterator end() const
Definition: array.hpp:511
Real back() const
Definition: array.hpp:458
Size size() const
dimension of the array
Definition: array.hpp:495
Real front() const
Definition: array.hpp:451
const_iterator begin() const
Definition: array.hpp:503
Brent 1-D solver
Definition: brent.hpp:37
calendar class
Definition: calendar.hpp:61
Date adjust(const Date &, BusinessDayConvention convention=Following) const
Definition: calendar.cpp:84
Calibrated model class.
Definition: model.hpp:86
Standard constant parameter .
Definition: parameter.hpp:71
Cubic interpolation between discrete points.
const std::vector< Real > & cCoefficients() const
const std::vector< Real > & bCoefficients() const
const std::vector< Real > & aCoefficients() const
Concrete date class.
Definition: date.hpp:125
static Date minDate()
earliest allowed date
Definition: date.cpp:766
static Date endOfMonth(const Date &d)
last day of the month to which the given date belongs
Definition: date.hpp:428
static Date advance(const Date &d, Integer units, TimeUnit)
Definition: date.cpp:139
Base error class.
Definition: errors.hpp:39
void enableExtrapolation(bool b=true)
enable extrapolation in subsequent calls
generalized Gauss-Hermite integration
static Real gaussianShiftedPolynomialIntegral(Real a, Real b, Real c, Real d, Real e, Real h, Real x0, Real x1)
ext::shared_ptr< VanillaSwap > underlyingSwap(const ext::shared_ptr< SwapIndex > &index, const Date &expiry, const Period &tenor) const
Real numeraire(Time t, Real y=0.0, const Handle< YieldTermStructure > &yts=Handle< YieldTermStructure >()) const
Real swapRate(const Date &fixing, const Period &tenor, const Date &referenceDate=Null< Date >(), Real y=0.0, const ext::shared_ptr< SwapIndex > &swapIdx=ext::shared_ptr< SwapIndex >()) const
Array yGrid(Real yStdDevs, int gridPoints, Real T=1.0, Real t=0, Real y=0) const
Real zerobond(Time T, Time t=0.0, Real y=0.0, const Handle< YieldTermStructure > &yts=Handle< YieldTermStructure >()) const
ext::shared_ptr< StochasticProcess1D > stateProcess_
Shared handle to an observable.
Definition: handle.hpp:41
bool empty() const
checks if the contained shared pointer points to anything
Definition: handle.hpp:166
virtual void calculate() const
Definition: lazyobject.hpp:253
std::vector< Date > volstepdates_
Real zerobondImpl(Time T, Time t, Real y, const Handle< YieldTermStructure > &yts) const override
Array zerobondArray(Time T, Time t, const Array &y) const
const ModelOutputs & modelOutputs() const
Handle< SwaptionVolatilityStructure > swaptionVol_
ext::shared_ptr< SwapIndex > swapIndexBase_
Real swapRateInternal(const Date &fixing, const Period &tenor, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< SwapIndex > swapIdx=ext::shared_ptr< SwapIndex >()) const
std::vector< std::pair< Size, Size > > forcedArbitrageIndices_
Real swapAnnuityInternal(const Date &fixing, const Period &tenor, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< SwapIndex > swapIdx=ext::shared_ptr< SwapIndex >()) const
Handle< OptionletVolatilityStructure > capletVol_
Array deflatedZerobondArray(Time T, Time t, const Array &y) const
std::map< Date, CalibrationPoint > calibrationPoints_
ext::shared_ptr< IborIndex > iborIndex_
ext::shared_ptr< Matrix > discreteNumeraire_
Real marketDigitalPrice(const Date &expiry, const CalibrationPoint &p, const Option::Type &type, Real strike) const
MarkovFunctional(const Handle< YieldTermStructure > &termStructure, Real reversion, std::vector< Date > volstepdates, std::vector< Real > volatilities, const Handle< SwaptionVolatilityStructure > &swaptionVol, const std::vector< Date > &swaptionExpiries, const std::vector< Period > &swaptionTenors, const ext::shared_ptr< SwapIndex > &swapIndexBase, MarkovFunctional::ModelSettings modelSettings=ModelSettings())
Real swaptionPriceInternal(const Option::Type &type, const Date &expiry, const Period &tenor, Rate strike, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, const ext::shared_ptr< SwapIndex > &swapIdx=ext::shared_ptr< SwapIndex >()) const
Real deflatedZerobond(Time T, Time t=0.0, Real y=0.0) const
void makeCapletCalibrationPoint(const Date &expiry)
std::vector< Period > swaptionTenors_
std::vector< Date > swaptionExpiries_
std::vector< ext::shared_ptr< Interpolation > > numeraire_
std::vector< Date > capletExpiries_
std::vector< Real > volatilities_
std::vector< std::pair< Size, Size > > arbitrageIndices_
Array numeraireArray(Time t, const Array &y) const
void makeSwaptionCalibrationPoint(const Date &expiry, const Period &tenor)
Real marketSwapRate(const Date &expiry, const CalibrationPoint &p, Real digitalPrice, Real guess=0.03, Real shift=0.0) const
Real forwardRateInternal(const Date &fixing, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< IborIndex > iborIdx=ext::shared_ptr< IborIndex >()) const
std::vector< Time > volsteptimes_
Real capletPriceInternal(const Option::Type &type, const Date &expiry, Rate strike, const Date &referenceDate=Null< Date >(), Real y=0.0, bool zeroFixingDays=false, ext::shared_ptr< IborIndex > iborIdx=ext::shared_ptr< IborIndex >()) const
Real numeraireImpl(Time t, Real y, const Handle< YieldTermStructure > &yts) const override
const Time & numeraireTime() const
No constraint.
Definition: constraint.hpp:79
template class providing a null value for a given type.
Definition: null.hpp:76
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
void setParam(Size i, Real x)
Definition: parameter.hpp:51
const Array & params() const
Definition: parameter.hpp:50
Size size() const
Definition: parameter.hpp:55
Piecewise-constant parameter.
Definition: parameter.hpp:119
Constraint imposing positivity to all arguments
Definition: constraint.hpp:92
Payment schedule.
Definition: schedule.hpp:40
const Calendar & calendar() const
Definition: schedule.hpp:176
const std::vector< Date > & dates() const
Definition: schedule.hpp:75
const Date & date(Size i) const
Definition: schedule.hpp:160
Size size() const
Definition: schedule.hpp:69
const std::vector< Real > & strikeGrid() const
const std::vector< Real > & moneyGrid() const
const Handle< YieldTermStructure > & termStructure() const
Definition: model.hpp:77
BusinessDayConvention
Business Day conventions.
@ Annual
once a year
Definition: frequency.hpp:39
#define QL_EPSILON
Definition: qldefines.hpp:178
#define QL_MAX_INTEGER
Definition: qldefines.hpp:174
Real Time
continuous quantity with 1-year units
Definition: types.hpp:62
QL_REAL Real
real number
Definition: types.hpp:50
QL_INTEGER Integer
integer number
Definition: types.hpp:35
Real Rate
interest rates
Definition: types.hpp:70
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
Real months(const Period &p)
Definition: period.cpp:296
std::ostream & operator<<(std::ostream &out, GFunctionFactory::YieldCurveModel type)
STL namespace.
ext::shared_ptr< SmileSection > smileSection_
std::vector< std::vector< Real > > modelPutPremium_
std::vector< std::vector< Real > > marketPutPremium_
std::vector< std::vector< Real > > smileStrikes_
std::vector< std::vector< Real > > modelCallPremium_
std::vector< std::vector< Real > > marketVega_
std::vector< std::vector< Real > > marketCallPremium_
std::vector< std::vector< Real > > marketRawCallPremium_
std::vector< std::vector< Real > > marketRawPutPremium_
ext::shared_ptr< CustomSmileFactory > customSmileFactory_