Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
durationadjustedcmscoupontsrpricer.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2021 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
21
22#include <ql/cashflows/cmscoupon.hpp>
23#include <ql/cashflows/fixedratecoupon.hpp>
24#include <ql/cashflows/iborcoupon.hpp>
25#include <ql/cashflows/lineartsrpricer.hpp>
26#include <ql/indexes/iborindex.hpp>
27#include <ql/instruments/vanillaswap.hpp>
28#include <ql/math/integrals/kronrodintegral.hpp>
29#include <ql/math/solvers1d/brent.hpp>
30#include <ql/pricingengines/blackformula.hpp>
31#include <ql/quotes/simplequote.hpp>
32#include <ql/termstructures/volatility/atmsmilesection.hpp>
33#include <ql/time/schedule.hpp>
34
35namespace QuantExt {
36
38 const Handle<SwaptionVolatilityStructure>& swaptionVol,
39 const QuantLib::ext::shared_ptr<AnnuityMappingBuilder>& annuityMappingBuilder, const Real lowerIntegrationBound,
40 const Real upperIntegrationBound, const QuantLib::ext::shared_ptr<Integrator>& integrator)
41 : CmsCouponPricer(swaptionVol), annuityMappingBuilder_(annuityMappingBuilder),
42 lowerIntegrationBound_(lowerIntegrationBound), upperIntegrationBound_(upperIntegrationBound),
43 integrator_(integrator) {
44 if (integrator_ == nullptr)
45 integrator_ = ext::make_shared<GaussKronrodNonAdaptive>(1E-10, 5000, 1E-10);
46 if (annuityMappingBuilder_ != nullptr)
47 registerWith(annuityMappingBuilder_);
48}
49
51 QL_FAIL("DurationAdjustedCmsCouponTsrPricer::swapletPrice() is not implemented");
52}
53
55 QL_FAIL("DurationAdjustedCmsCouponTsrPricer::swapletPrice() is not implemented");
56}
57
59 QL_FAIL("DurationAdjustedCmsCouponTsrPricer::swapletPrice() is not implemented");
60}
61
64 (coupon_->gearing() * swapRate_ + coupon_->spread()) * durationAdjustment_;
65}
66
68 if (coupon_->fixingDate() <= today_) {
69 return durationAdjustment_ * coupon_->gearing() * std::max(swapRate_ - effectiveCap, 0.0);
70 } else {
71 return durationAdjustment_ * coupon_->gearing() * optionletRate(Option::Call, effectiveCap);
72 }
73}
74
75Rate DurationAdjustedCmsCouponTsrPricer::floorletRate(Rate effectiveFloor) const {
76 if (coupon_->fixingDate() <= today_) {
77 return durationAdjustment_ * coupon_->gearing() * std::max(effectiveFloor - swapRate_, 0.0);
78 } else {
79 return durationAdjustment_ * coupon_->gearing() * optionletRate(Option::Put, effectiveFloor);
80 }
81}
82
84
85 coupon_ = dynamic_cast<const DurationAdjustedCmsCoupon*>(&coupon);
86 QL_REQUIRE(coupon_, "DurationAdjustedCmsCoupon needed");
87 today_ = QuantLib::Settings::instance().evaluationDate();
89
90 if (coupon_->fixingDate() > today_) {
91 Handle<YieldTermStructure> discountCurve;
92 if (coupon_->swapIndex()->exogenousDiscount())
93 discountCurve = coupon_->swapIndex()->discountingTermStructure();
94 else
95 discountCurve = coupon_->swapIndex()->forwardingTermStructure();
96 auto swap = coupon_->swapIndex()->underlyingSwap(coupon_->fixingDate());
97 swapRate_ = swap->fairRate();
98 forwardAnnuity_ = 1.0E4 * std::fabs(swap->fixedLegBPS()) / discountCurve->discount(coupon_->date());
99 smileSection_ = swaptionVolatility()->smileSection(coupon_->fixingDate(), coupon_->swapIndex()->tenor());
100 // if the smile section does not have an ATM level, we add one
101 if (smileSection_->atmLevel() == Null<Real>())
102 smileSection_ = ext::make_shared<AtmSmileSection>(smileSection_, swapRate_);
104 annuityMappingBuilder_->build(today_, coupon_->fixingDate(), coupon_->date(), *swap, discountCurve);
105 } else {
106 swapRate_ = coupon_->swapIndex()->fixing(coupon_->fixingDate());
107 }
108}
109
110Real DurationAdjustedCmsCouponTsrPricer::optionletRate(Option::Type optionType, Real strike) const {
111
112 Real effectiveLowerIntegrationBound = lowerIntegrationBound_;
113 Real effectiveUpperIntegrationBound = upperIntegrationBound_;
114
115 // adjust the lower integration bound if the vol type is lognormal
116
117 if (swaptionVolatility()->volatilityType() == ShiftedLognormal) {
118 effectiveLowerIntegrationBound = std::max(
119 lowerIntegrationBound_, -swaptionVolatility()->shift(coupon_->fixingDate(), coupon_->swapIndex()->tenor()));
120 }
121
122 // the payoff function and its 1st and 2nd derivatives vanish for arguments > strike (call) resp. < strike (put)
123
124 if (optionType == Option::Call)
125 effectiveLowerIntegrationBound = std::max(effectiveLowerIntegrationBound, strike);
126 else
127 effectiveUpperIntegrationBound = std::min(effectiveUpperIntegrationBound, strike);
128
129 Real integral = 0.0;
130 Real omega = optionType == Option::Call ? 1.0 : -1.0;
131
132 // compute the relevant integral
133
134 if (effectiveLowerIntegrationBound < effectiveUpperIntegrationBound &&
135 !close_enough(effectiveLowerIntegrationBound, effectiveUpperIntegrationBound)) {
136
137 auto f = [this, strike, omega](const Real S) -> Real {
138 return std::max(omega * (S - strike), 0.0) * this->durationAdjustment_;
139 };
140
141 auto fp = [this, strike, omega](const Real S) -> Real {
142 if (this->coupon_->duration() == 0) {
143 return omega * (omega * S > omega * strike ? 1.0 : 0.0);
144 } else {
145 Real dp = 0.0;
146 for (Size i = 0; i < this->coupon_->duration(); ++i) {
147 dp -= static_cast<Real>(i + 1) / std::pow(1.0 + this->swapRate_, i + 2);
148 }
149 return this->durationAdjustment_ * omega * (omega * S > omega * strike ? 1.0 : 0.0) +
150 dp * std::max(omega * (S - strike), 0.0);
151 }
152 };
153
154 auto fpp = [this, strike, omega](const Real S) -> Real {
155 if (this->coupon_->duration() == 0) {
156 return 0.0;
157 } else {
158 Real dp1 = 0.0, dp2 = 0.0;
159 for (Size i = 0; i < this->coupon_->duration(); ++i) {
160 dp1 -= static_cast<Real>(i + 1) / std::pow(1.0 + this->swapRate_, i + 2);
161 dp2 += static_cast<Real>(i + 1) * static_cast<Real>(i + 2) / std::pow(1.0 + this->swapRate_, i + 3);
162 }
163 return dp1 * omega * (omega * S > omega * strike ? 1.0 : 0.0) +
164 dp2 * std::max(omega * (S - strike), 0.0);
165 }
166 };
167
168 auto integrand = [this, &f, &fp, &fpp](const Real S) -> Real {
169 Real f2 = 0.0;
170 if (this->coupon_->duration() != 0) {
171 f2 = this->annuityMapping_->map(S) * fpp(S);
172 }
173 Real f1 = 2.0 * fp(S) * this->annuityMapping_->mapPrime(S);
174 Real f0 = 0.0;
175 if (!this->annuityMapping_->mapPrime2IsZero())
176 f0 = annuityMapping_->mapPrime2(S) * f(S);
177 return (f0 + f1 + f2) * this->smileSection_->optionPrice(S, S < swapRate_ ? Option::Put : Option::Call);
178 };
179
180 // calculate two integrals up to and from the fair swap rate => is that better than a single integral?
181 Real tmpBound;
182 tmpBound = std::min(effectiveUpperIntegrationBound, swapRate_);
183 if (tmpBound > effectiveLowerIntegrationBound)
184 integral += (*integrator_)(integrand, effectiveLowerIntegrationBound, tmpBound);
185 tmpBound = std::max(effectiveLowerIntegrationBound, swapRate_);
186 if (effectiveUpperIntegrationBound > tmpBound)
187 integral += (*integrator_)(integrand, tmpBound, effectiveUpperIntegrationBound);
188
189 integral = (*integrator_)(integrand, effectiveLowerIntegrationBound, effectiveUpperIntegrationBound);
190 }
191
192 // add payoff function * annuityMapping at fair swap rate and Dirac delta contributions from integral
193
194 Real singularTerms =
195 annuityMapping_->map(swapRate_) * durationAdjustment_ * std::max(omega * (swapRate_ - strike), 0.0) +
196 annuityMapping_->map(strike) * durationAdjustment_ *
197 smileSection_->optionPrice(strike, strike < swapRate_ ? Option::Put : Option::Call);
198
199 return forwardAnnuity_ * (integral + singularTerms) / durationAdjustment_;
200}
201
202} // namespace QuantExt
const QuantLib::ext::shared_ptr< SwapIndex > & swapIndex() const
QuantLib::ext::shared_ptr< SmileSection > smileSection_
Real optionletRate(Option::Type type, Real effectiveStrike) const
void initialize(const FloatingRateCoupon &coupon) override
QuantLib::ext::shared_ptr< AnnuityMappingBuilder > annuityMappingBuilder_
DurationAdjustedCmsCouponTsrPricer(const Handle< SwaptionVolatilityStructure > &swaptionVol, const QuantLib::ext::shared_ptr< AnnuityMappingBuilder > &annuityMappingBuilder, const Real lowerIntegrationBound=-0.3, const Real upperIntegrationBound=0.3, const QuantLib::ext::shared_ptr< Integrator > &integrator=QuantLib::ext::shared_ptr< Integrator >())
QuantLib::ext::shared_ptr< AnnuityMapping > annuityMapping_
cms coupon scaled by a duration number
tsr coupon pricer for duration adjusted cms coupon
Real integral(const CrossAssetModel &model, const E &e, const Real a, const Real b)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)