Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
analyticlgmswaptionengine.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 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 <ql/cashflows/iborcoupon.hpp>
20#include <ql/cashflows/overnightindexedcoupon.hpp>
21#include <ql/indexes/iborindex.hpp>
22#include <ql/math/solvers1d/brent.hpp>
24
25#include <boost/bind/bind.hpp>
26
27#include <ostream>
28
29namespace QuantExt {
30
31AnalyticLgmSwaptionEngine::AnalyticLgmSwaptionEngine(const QuantLib::ext::shared_ptr<LinearGaussMarkovModel>& model,
32 const Handle<YieldTermStructure>& discountCurve,
33 const FloatSpreadMapping floatSpreadMapping)
34 : GenericEngine<Swaption::arguments, Swaption::results>(), p_(model->parametrization()),
35 c_(discountCurve.empty() ? p_->termStructure() : discountCurve), floatSpreadMapping_(floatSpreadMapping),
36 caching_(false) {
37 registerWith(model);
38 registerWith(c_);
39}
40
41AnalyticLgmSwaptionEngine::AnalyticLgmSwaptionEngine(const QuantLib::ext::shared_ptr<CrossAssetModel>& model, const Size ccy,
42 const Handle<YieldTermStructure>& discountCurve,
43 const FloatSpreadMapping floatSpreadMapping)
44 : GenericEngine<Swaption::arguments, Swaption::results>(), p_(model->irlgm1f(ccy)),
45 c_(discountCurve.empty() ? p_->termStructure() : discountCurve), floatSpreadMapping_(floatSpreadMapping),
46 caching_(false) {
47 registerWith(model);
48 registerWith(c_);
49}
50
51AnalyticLgmSwaptionEngine::AnalyticLgmSwaptionEngine(const QuantLib::ext::shared_ptr<IrLgm1fParametrization> irlgm1f,
52 const Handle<YieldTermStructure>& discountCurve,
53 const FloatSpreadMapping floatSpreadMapping)
54 : GenericEngine<Swaption::arguments, Swaption::results>(), p_(irlgm1f),
55 c_(discountCurve.empty() ? p_->termStructure() : discountCurve), floatSpreadMapping_(floatSpreadMapping),
56 caching_(false) {
57 registerWith(c_);
58}
59
60void AnalyticLgmSwaptionEngine::enableCache(const bool lgm_H_constant, const bool lgm_alpha_constant) {
61 caching_ = true;
62 lgm_H_constant_ = lgm_H_constant;
63 lgm_alpha_constant_ = lgm_alpha_constant;
64 clearCache();
65}
66
68 S_.clear(); // indicates that H / alpha independent variables are not yet computed
69 Hj_.clear(); // indicates that H dependent variables not yet computed
70 zetaex_ = Null<Real>(); // indicates that alpha dependent variables are not yet computed
71}
72
73Real AnalyticLgmSwaptionEngine::flatAmount(const Size k) const {
74 Date reference = p_->termStructure()->referenceDate();
75 QuantLib::ext::shared_ptr<IborIndex> index =
76 arguments_.swap ? arguments_.swap->iborIndex() : arguments_.swapOis->overnightIndex();
77 if (arguments_.swapOis) {
78 auto on = QuantLib::ext::dynamic_pointer_cast<QuantLib::OvernightIndexedCoupon>(floatingLeg_[k]);
79 QL_REQUIRE(on, "AnalyticalLgmSwaptionEngine::calculate(): internal error, could not cast to "
80 "QuantLib::OvernightIndexedCoupon.");
81 QL_REQUIRE(!on->valueDates().empty(),
82 "AnalyticalLgmSwaptionEngine::calculate(): internal error, no value dates in ois coupon.");
83 Date v1 = std::max(reference, on->valueDates().front());
84 Date v2 = std::max(v1 + 1, on->valueDates().back());
85 Real rate;
86 if (on->averagingMethod() == QuantLib::RateAveraging::Compound)
87 rate = (c_->discount(v1) / c_->discount(v2) - 1.0) / index->dayCounter().yearFraction(v1, v2);
88 else
89 rate = std::log(c_->discount(v1) / c_->discount(v2)) / index->dayCounter().yearFraction(v1, v2);
90 return floatingLeg_[k]->accrualPeriod() * nominal_ * rate;
91 } else {
92 if (IborCoupon::Settings::instance().usingAtParCoupons()) {
93 // if par coupons are used, we mimick the fixing estimation in IborCoupon; we make
94 // sure that the estimation period does not start in the past and we do not use
95 // historical fixings
96 Date fixingValueDate =
97 index->fixingCalendar().advance(floatingLeg_[k]->fixingDate(), index->fixingDays(), Days);
98 fixingValueDate = std::max(fixingValueDate, reference);
99 auto cpn = QuantLib::ext::dynamic_pointer_cast<Coupon>(floatingLeg_[k]);
100 QL_REQUIRE(cpn, "AnalyticalLgmSwaptionEngine::calculate(): coupon expected on underlying swap "
101 "floating leg, could not cast");
102 Date nextFixingDate = index->fixingCalendar().advance(cpn->accrualEndDate(),
103 -static_cast<Integer>(index->fixingDays()), Days);
104 Date fixingEndDate = index->fixingCalendar().advance(nextFixingDate, index->fixingDays(), Days);
105 fixingEndDate = std::max(fixingEndDate, fixingValueDate + 1);
106 Real spanningTime = index->dayCounter().yearFraction(fixingValueDate, fixingEndDate);
107 DiscountFactor disc1 = c_->discount(fixingValueDate);
108 DiscountFactor disc2 = c_->discount(fixingEndDate);
109 Real fixing = (disc1 / disc2 - 1.0) / spanningTime;
110 return fixing * floatingLeg_[k]->accrualPeriod() * nominal_;
111 } else {
112 // if indexed coupons are used, we use a proper fixing, but make sure that the fixing
113 // date is not in the past and we do not use a historical fixing for "today"
114 auto flatIbor = QuantLib::ext::make_shared<IborIndex>(
115 index->familyName() + " (no fixings)", index->tenor(), index->fixingDays(), index->currency(),
116 index->fixingCalendar(), index->businessDayConvention(), index->endOfMonth(), index->dayCounter(), c_);
117 Date fixingDate = flatIbor->fixingCalendar().adjust(std::max(floatingLeg_[k]->fixingDate(), reference));
118 return flatIbor->fixing(fixingDate) * floatingLeg_[k]->accrualPeriod() * nominal_;
119 }
120 }
121}
122
124
125 QL_REQUIRE(arguments_.settlementType == Settlement::Physical, "cash-settled swaptions are not supported ...");
126
127 Date reference = p_->termStructure()->referenceDate();
128
129 Date expiry = arguments_.exercise->dates().back();
130
131 if (expiry <= reference) {
132 // swaption is expired, possibly generated swap is not
133 // valued by this engine, so we set the npv to zero
134 results_.value = 0.0;
135 return;
136 }
137
138 if (!caching_ || S_.empty()) {
139
140 Option::Type type = arguments_.type == VanillaSwap::Payer ? Option::Call : Option::Put;
141
142 QL_REQUIRE(
143 arguments_.swap || arguments_.swapOis,
144 "AnalyticalLgmSwaptionEngine::calculate(): internal error, expected either swap or swapOis to be set.");
145 const Schedule& fixedSchedule =
146 arguments_.swap ? arguments_.swap->fixedSchedule() : arguments_.swapOis->schedule();
147 const Schedule& floatSchedule =
148 arguments_.swap ? arguments_.swap->floatingSchedule() : arguments_.swapOis->schedule();
149
150 j1_ = std::lower_bound(fixedSchedule.dates().begin(), fixedSchedule.dates().end(), expiry) -
151 fixedSchedule.dates().begin();
152 k1_ = std::lower_bound(floatSchedule.dates().begin(), floatSchedule.dates().end(), expiry) -
153 floatSchedule.dates().begin();
154
155 nominal_ = arguments_.swap ? arguments_.swap->nominal() : arguments_.swapOis->nominal();
156
157 fixedLeg_.clear();
158 floatingLeg_.clear();
159 for(auto const& c: (arguments_.swap ? arguments_.swap->fixedLeg() : arguments_.swapOis->fixedLeg())) {
160 fixedLeg_.push_back(QuantLib::ext::dynamic_pointer_cast<FixedRateCoupon>(c));
161 QL_REQUIRE(fixedLeg_.back(),
162 "AnalyticalLgmSwaptionEngine::calculate(): internal error, could not cast to FixedRateCoupon");
163 }
164 for(auto const& c: (arguments_.swap ? arguments_.swap->floatingLeg() : arguments_.swapOis->overnightLeg())) {
165 floatingLeg_.push_back(QuantLib::ext::dynamic_pointer_cast<FloatingRateCoupon>(c));
166 QL_REQUIRE(
167 floatingLeg_.back(),
168 "AnalyticalLgmSwaptionEngine::calculate(): internal error, could not cast to FloatingRateRateCoupon");
169 }
170
171 // compute S_i, i.e. equivalent fixed rate spreads compensating for
172 // a) a possibly non-zero float spread and
173 // b) a spread between the ibor indices forwarding curve and the
174 // discounting curve
175 // here, we do not work with a spread corrections directly, but
176 // with this multiplied by the nominal and accrual basis,
177 // so S_i is really an amount correction.
178
179 S_.resize(fixedLeg_.size() - j1_);
180 for (Size i = 0; i < S_.size(); ++i) {
181 S_[i] = 0.0;
182 }
183 S_m1 = 0.0;
184 Size ratio =
185 static_cast<Size>(static_cast<Real>(floatingLeg_.size()) / static_cast<Real>(fixedLeg_.size()) + 0.5);
186 QL_REQUIRE(ratio >= 1, "floating leg's payment frequency must be equal or "
187 "higher than fixed leg's payment frequency in "
188 "analytic lgm swaption engine");
189
191 Real annuity = 0.0;
192 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
193 annuity += nominal_ * fixedLeg_[j]->accrualPeriod() * c_->discount(fixedLeg_[j]->date());
194 }
195 Real floatAmountMismatch = 0.0;
196 for(Size k=k1_; k<floatingLeg_.size();++k) {
197 floatAmountMismatch +=
198 (floatingLeg_[k]->amount() - flatAmount(k)) * c_->discount(floatingLeg_[k]->date());
199 }
200 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
201 S_[j] = fixedLeg_[j]->accrualPeriod() * nominal_ * floatAmountMismatch / annuity;
202 }
203 }
204
205 Size k = k1_;
206 // The method reduces the problem to a one curve configuration w.r.t. the discount curve and
207 // apply a correction for the discount curve / forwarding curve spread. Furthermore the method
208 // assumes that no historical fixings are present in the floating rate coupons.
209 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
210 Real sum1 = 0.0, sum2 = 0.0;
211 for (Size rr = 0; rr < ratio && k < floatingLeg_.size(); ++rr, ++k) {
212 Real amount = Null<Real>();
213 // same strategy as in VanillaSwap::setupArguments()
214 try {
215 amount = floatingLeg_[k]->amount();
216 } catch (...) {
217 }
218 Real lambda1, lambda2;
220 // we do not use the exact pay dates but the ratio to determine
221 // the distance to the adjacent payment dates
222 lambda2 = static_cast<Real>(rr + 1) / static_cast<Real>(ratio);
223 lambda1 = 1.0 - lambda2;
224 } else if (floatSpreadMapping_ == nextCoupon) {
225 lambda1 = 0.0;
226 lambda2 = 1.0;
227 } else {
228 lambda1 = lambda2 = 0.0;
229 }
230 if (amount != Null<Real>()) {
231 Real correction = (amount - flatAmount(k)) * c_->discount(floatingLeg_[k]->date());
232 sum1 += lambda1 * correction;
233 sum2 += lambda2 * correction;
234 } else {
235 // if no amount is given, we do not need a spread correction
236 // due to different forward / discounting curves since then
237 // no curve is attached to the swap's ibor index and so we
238 // assume a one curve setup;
239 // but we can still have a float spread that has to be converted
240 // into a fixed leg's payment
241 Real correction = nominal_ * floatingLeg_[k]->spread() * floatingLeg_[k]->accrualPeriod() *
242 c_->discount(floatingLeg_[k]->date());
243 sum1 += lambda1 * correction;
244 sum2 += lambda2 * correction;
245 }
246 }
247 if (j > j1_) {
248 S_[j - j1_ - 1] += sum1 / c_->discount(fixedLeg_[j - 1]->date());
249 } else {
250 S_m1 += sum1 / c_->discount(floatingLeg_[k1_]->accrualStartDate());
251 }
252 S_[j - j1_] += sum2 / c_->discount(fixedLeg_[j]->date());
253 }
254
255 w_ = type == Option::Call ? -1.0 : 1.0;
256 D0_ = c_->discount(floatingLeg_[k1_]->accrualStartDate());
257 Dj_.resize(fixedLeg_.size() - j1_);
258 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
259 Dj_[j - j1_] = c_->discount(fixedLeg_[j - j1_]->date());
260 }
261 }
262
263 if (!caching_ || !lgm_H_constant_ || Hj_.empty()) {
264 // it is a requirement that H' does not change its sign,
265 // with u = -1.0 we handle the case H' < 0
266 u_ = p_->Hprime(0.0) > 0.0 ? 1.0 : -1.0;
267
268 H0_ = p_->H(p_->termStructure()->timeFromReference(floatingLeg_[k1_]->accrualStartDate()));
269 Hj_.resize(fixedLeg_.size() - j1_);
270 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
271 Hj_[j - j1_] = p_->H(p_->termStructure()->timeFromReference(fixedLeg_[j]->date()));
272 }
273 }
274
275 if (!caching_ || !lgm_alpha_constant_ || zetaex_ == Null<Real>()) {
276 zetaex_ = p_->zeta(p_->termStructure()->timeFromReference(expiry));
277 }
278
279 Brent b;
280 Real yStar;
281 try {
282 yStar = b.solve(QuantLib::ext::bind(&AnalyticLgmSwaptionEngine::yStarHelper, this, QuantLib::ext::placeholders::_1), 1.0E-6,
283 0.0, 0.01);
284 } catch (const std::exception& e) {
285 std::ostringstream os;
286 os << "AnalyticLgmSwaptionEngine: failed to compute yStar (" << e.what() << "), parameter details: [";
287 Real tte = p_->termStructure()->timeFromReference(expiry);
288 os << "tte=" << tte << ", vol=" << std::sqrt(zetaex_ / tte) << ", nominal=" << nominal_
289 << ", d=" << D0_;
290 for (Size j = 0; j < Dj_.size(); ++j)
291 os << ", d" << j << "=" << Dj_[j];
292 os << ", h=" << H0_;
293 for (Size j = 0; j < Hj_.size(); ++j)
294 os << ", h" << j << "=" << Hj_[j];
295 for (Size i = j1_; i < fixedLeg_.size(); ++i) {
296 os << ", cpn" << i << "=(" << QuantLib::io::iso_date(fixedLeg_[i]->accrualStartDate()) << ","
297 << QuantLib::io::iso_date(fixedLeg_[i]->date()) << "," << fixedLeg_[i]->amount() << ")";
298 }
299 os << ", S=" << S_m1;
300 for (Size j = 0; j < S_.size(); ++j) {
301 os << ", S" << j << "=" << S_[j];
302 }
303 os << "]";
304 QL_FAIL(os.str());
305 }
306
307 CumulativeNormalDistribution N;
308 Real sqrt_zetaex = std::sqrt(zetaex_);
309 Real sum = 0.0;
310 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
311 sum += w_ * (fixedLeg_[j]->amount() - S_[j - j1_]) * Dj_[j - j1_] *
312 N(u_ * w_ * (yStar + (Hj_[j - j1_] - H0_) * zetaex_) / sqrt_zetaex);
313 }
314 sum += -w_ * S_m1 * D0_ * N(u_ * w_ * yStar / sqrt_zetaex);
315 sum += w_ * (nominal_ * Dj_.back() * N(u_ * w_ * (yStar + (Hj_.back() - H0_) * zetaex_) / sqrt_zetaex) -
316 nominal_ * D0_ * N(u_ * w_ * yStar / sqrt_zetaex));
317 results_.value = sum;
318
319 results_.additionalResults["fixedAmountCorrectionSettlement"] = S_m1;
320 results_.additionalResults["fixedAmountCorrections"] = S_;
321
322} // calculate
323
325 Real sum = 0.0;
326 for (Size j = j1_; j < fixedLeg_.size(); ++j) {
327 sum += (fixedLeg_[j]->amount() - S_[j - j1_]) * Dj_[j - j1_] *
328 std::exp(-(Hj_[j - j1_] - H0_) * y - 0.5 * (Hj_[j - j1_] - H0_) * (Hj_[j - j1_] - H0_) * zetaex_);
329 }
330 sum += -S_m1 * D0_;
331 sum += Dj_.back() * nominal_ *
332 std::exp(-(Hj_.back() - H0_) * y - 0.5 * (Hj_.back() - H0_) * (Hj_.back() - H0_) * zetaex_);
333 sum -= D0_ * nominal_;
334 return sum;
335}
336
337} // namespace QuantExt
analytic engine for european swaptions in the LGM model
const Instrument::results * results_
Definition: cdsoption.cpp:81
QuantLib::ext::shared_ptr< IrLgm1fParametrization > p_
std::vector< QuantLib::ext::shared_ptr< FixedRateCoupon > > fixedLeg_
AnalyticLgmSwaptionEngine(const QuantLib::ext::shared_ptr< LinearGaussMarkovModel > &model, const Handle< YieldTermStructure > &discountCurve=Handle< YieldTermStructure >(), const FloatSpreadMapping floatSpreadMapping=proRata)
void enableCache(const bool lgm_H_constant=true, const bool lgm_alpha_constant=false)
std::vector< QuantLib::ext::shared_ptr< FloatingRateCoupon > > floatingLeg_
Real sum(const Cash &c, const Cash &d)
Definition: bondbasket.cpp:107
Swap::arguments * arguments_