QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
haganirregularswaptionengine.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2011, 2012, 2013, 2023 Andre Miemiec
5 Copyright (C) 2012 Samuel Tebege
6 This file is part of QuantLib, a free-software/open-source library
7 for financial quantitative analysts and developers - http://quantlib.org/
8 QuantLib is free software: you can redistribute it and/or modify it
9 under the terms of the QuantLib license. You should have received a
10 copy of the license along with this program; if not, please email
11 <quantlib-dev@lists.sf.net>. The license is also available online at
12 <http://quantlib.org/license.shtml>.
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the license for more details.
16*/
17
18#include <ql/cashflows/cashflows.hpp>
19#include <ql/cashflows/couponpricer.hpp>
20#include <ql/exercise.hpp>
21#include <ql/experimental/swaptions/haganirregularswaptionengine.hpp>
22#include <ql/instruments/swaption.hpp>
23#include <ql/math/distributions/normaldistribution.hpp>
24#include <ql/math/interpolations/linearinterpolation.hpp>
25#include <ql/math/matrixutilities/svd.hpp>
26#include <ql/math/solvers1d/bisection.hpp>
27#include <ql/math/solvers1d/brent.hpp>
28#include <ql/pricingengines/swap/discountingswapengine.hpp>
29#include <ql/pricingengines/swaption/blackswaptionengine.hpp>
30#include <utility>
31
32namespace QuantLib {
33
35 // Implementation of helper class HaganIrregularSwaptionEngine::Basket //
37
39 ext::shared_ptr<IrregularSwap> swap,
40 Handle<YieldTermStructure> termStructure,
41 Handle<SwaptionVolatilityStructure> volatilityStructure)
42 : swap_(std::move(swap)), termStructure_(std::move(termStructure)),
43 volatilityStructure_(std::move(volatilityStructure)) {
44
45 engine_ = ext::shared_ptr<PricingEngine>(new DiscountingSwapEngine(termStructure_));
46
47 // store swap npv
48 swap_->setPricingEngine(engine_);
49 targetNPV_ = swap_->NPV();
50
51 // build standard swaps
52
53 const Leg& fixedLeg = swap_->fixedLeg();
54 const Leg& floatLeg = swap_->floatingLeg();
55
56 Leg fixedCFS, floatCFS;
57
58 for (Size i = 0; i < fixedLeg.size(); ++i) {
59 // retrieve fixed rate coupon from fixed leg
60 ext::shared_ptr<FixedRateCoupon> coupon =
61 ext::dynamic_pointer_cast<FixedRateCoupon>(fixedLeg[i]);
62 QL_REQUIRE(coupon, "dynamic cast of fixed leg coupon failed.");
63
64 expiries_.push_back(coupon->date());
65
66 ext::shared_ptr<FixedRateCoupon> newCpn = ext::make_shared<FixedRateCoupon>(
67 coupon->date(), 1.0, coupon->rate(), coupon->dayCounter(),
68 coupon->accrualStartDate(), coupon->accrualEndDate(),
69 coupon->referencePeriodStart(), coupon->referencePeriodEnd());
70
71 fixedCFS.push_back(newCpn);
72
73 annuities_.push_back(10000 * CashFlows::bps(fixedCFS, **termStructure_, true));
74
75 floatCFS.clear();
76
77 for (const auto& j : floatLeg) {
78 // retrieve ibor coupon from floating leg
79 ext::shared_ptr<IborCoupon> coupon = ext::dynamic_pointer_cast<IborCoupon>(j);
80 QL_REQUIRE(coupon, "dynamic cast of float leg coupon failed.");
81
82 if (coupon->date() <= expiries_[i]) {
83 ext::shared_ptr<IborCoupon> newCpn = ext::make_shared<IborCoupon>(
84 coupon->date(), 1.0, coupon->accrualStartDate(), coupon->accrualEndDate(),
85 coupon->fixingDays(), coupon->iborIndex(), 1.0, coupon->spread(),
86 coupon->referencePeriodStart(), coupon->referencePeriodEnd(),
87 coupon->dayCounter(), coupon->isInArrears());
88
89
90 if (!newCpn->isInArrears())
91 newCpn->setPricer(
92 ext::shared_ptr<FloatingRateCouponPricer>(new BlackIborCouponPricer()));
93
94 floatCFS.push_back(newCpn);
95 }
96 }
97
98 Real floatLegNPV = CashFlows::npv(floatCFS, **termStructure_, true);
99
100 fairRates_.push_back(floatLegNPV / annuities_[i]);
101 }
102 }
103
104
105 // computes a replication of the swap in terms of a basket of vanilla swaps
106 // by solving a linear system of equation
108
109 // update members
110 lambda_ = lambda;
111
112 Size n = swap_->fixedLeg().size();
113
114 // build linear system of equations
115 Matrix arr(n, n, 0.0);
116 Array rhs(n);
117
118
119 // fill the matrix describing the linear system of equations by looping over rows
120 for (Size r = 0; r < n; ++r) {
121
122 ext::shared_ptr<FixedRateCoupon> cpn_r =
123 ext::dynamic_pointer_cast<FixedRateCoupon>(swap_->fixedLeg()[r]);
124 QL_REQUIRE(cpn_r, "Cast to fixed rate coupon failed.");
125
126 // looping over columns
127 for (Size c = r; c < n; ++c) {
128
129 // set homogenous part of lse
130 arr[r][c] = (fairRates_[c] + lambda_) * cpn_r->accrualPeriod();
131 }
132
133 // add nominal repayment for i-th swap
134 arr[r][r] += 1;
135 }
136
137
138 for (Size r = 0; r < n; ++r) {
139 ext::shared_ptr<FixedRateCoupon> cpn_r =
140 ext::dynamic_pointer_cast<FixedRateCoupon>(swap_->fixedLeg()[r]);
141
142 // set inhomogenity of lse
143 Real N_r = cpn_r->nominal();
144
145 if (r < n - 1) {
146
147 ext::shared_ptr<FixedRateCoupon> cpn_rp1 =
148 ext::dynamic_pointer_cast<FixedRateCoupon>(swap_->fixedLeg()[r + 1]);
149
150 Real N_rp1 = cpn_rp1->nominal();
151
152 rhs[r] = N_r * (cpn_r->rate()) * cpn_r->accrualPeriod() + (N_r - N_rp1);
153
154 } else {
155
156 rhs[r] = N_r * (cpn_r->rate()) * cpn_r->accrualPeriod() + N_r;
157 }
158 }
159
160
161 SVD svd(arr);
162
163 return svd.solveFor(rhs);
164 }
165
166
168
169 Array weights = compute(lambda);
170
171 Real defect = -targetNPV_;
172
173 for (Size i = 0; i < weights.size(); ++i)
174 defect -= Integer(swap_->type()) * lambda * weights[i] * annuities_[i];
175
176 return defect;
177 }
178
179
180 // creates a standard swap by deducing its conventions from market data objects
181 ext::shared_ptr<VanillaSwap> HaganIrregularSwaptionEngine::Basket::component(Size i) const {
182
183 ext::shared_ptr<IborCoupon> iborCpn =
184 ext::dynamic_pointer_cast<IborCoupon>(swap_->floatingLeg()[0]);
185 QL_REQUIRE(iborCpn, "dynamic cast of float leg coupon failed. Can't find index.");
186 ext::shared_ptr<IborIndex> iborIndex = iborCpn->iborIndex();
187
188
189 Period dummySwapLength = Period(1, Years);
190
191 ext::shared_ptr<VanillaSwap> memberSwap_ =
192 MakeVanillaSwap(dummySwapLength, iborIndex)
193 .withType(swap_->type())
194 .withEffectiveDate(swap_->startDate())
195 .withTerminationDate(expiries_[i])
198
199 Real stdAnnuity = 10000 * CashFlows::bps(memberSwap_->fixedLeg(), **termStructure_, true);
200
201 // compute annuity transformed rate
202 Rate transformedRate = (fairRates_[i] + lambda_) * annuities_[i] / stdAnnuity;
203
204 memberSwap_ = MakeVanillaSwap(dummySwapLength, iborIndex, transformedRate)
205 .withType(swap_->type())
206 .withEffectiveDate(swap_->startDate())
207 .withTerminationDate(expiries_[i])
210
211
212 return memberSwap_;
213 }
214
215
217 // Implementation of class HaganIrregularSwaptionEngine //
219
220
222 Handle<SwaptionVolatilityStructure> volatilityStructure,
223 Handle<YieldTermStructure> termStructure)
224 : termStructure_(std::move(termStructure)),
225 volatilityStructure_(std::move(volatilityStructure)) {
228 }
229
230
232
233 // check exercise type
234 ext::shared_ptr<Exercise> exercise_ = this->arguments_.exercise;
235 QL_REQUIRE(exercise_->type() == QuantLib::Exercise::European, "swaption must be european");
236
237 // extract the underlying irregular swap
238 ext::shared_ptr<IrregularSwap> swap_ = this->arguments_.swap;
239
240
241 // Reshuffle spread from float to fixed (, i.e. remove spread from float side by finding the
242 // adjusted fixed coupon such that the NPV of the swap stays constant).
243 Leg fixedLeg = swap_->fixedLeg();
244 Real fxdLgBPS = CashFlows::bps(fixedLeg, **termStructure_, true);
245
246 Leg floatLeg = swap_->floatingLeg();
247 Real fltLgNPV = CashFlows::npv(floatLeg, **termStructure_, true);
248 Real fltLgBPS = CashFlows::bps(floatLeg, **termStructure_, true);
249
250
251 Leg floatCFS, fixedCFS;
252
253 floatCFS.clear();
254
255 for (auto& j : floatLeg) {
256 // retrieve ibor coupon from floating leg
257 ext::shared_ptr<IborCoupon> coupon = ext::dynamic_pointer_cast<IborCoupon>(j);
258 QL_REQUIRE(coupon, "dynamic cast of float leg coupon failed.");
259
260 ext::shared_ptr<IborCoupon> newCpn = ext::make_shared<IborCoupon>(
261 coupon->date(), coupon->nominal(), coupon->accrualStartDate(),
262 coupon->accrualEndDate(), coupon->fixingDays(), coupon->iborIndex(),
263 coupon->gearing(), 0.0, coupon->referencePeriodStart(),
264 coupon->referencePeriodEnd(), coupon->dayCounter(), coupon->isInArrears());
265
266
267 if (!newCpn->isInArrears())
268 newCpn->setPricer(
269 ext::shared_ptr<FloatingRateCouponPricer>(new BlackIborCouponPricer()));
270
271 floatCFS.push_back(newCpn);
272 }
273
274
275 Real sprdLgNPV = fltLgNPV - CashFlows::npv(floatCFS, **termStructure_, true);
276 Rate avgSpread = sprdLgNPV / fltLgBPS / 10000;
277
278 Rate cpn_adjustment = avgSpread * fltLgBPS / fxdLgBPS;
279
280 fixedCFS.clear();
281
282 for (auto& i : fixedLeg) {
283 // retrieve fixed rate coupon from fixed leg
284 ext::shared_ptr<FixedRateCoupon> coupon = ext::dynamic_pointer_cast<FixedRateCoupon>(i);
285 QL_REQUIRE(coupon, "dynamic cast of fixed leg coupon failed.");
286
287 ext::shared_ptr<FixedRateCoupon> newCpn = ext::make_shared<FixedRateCoupon>(
288 coupon->date(), coupon->nominal(), coupon->rate() - cpn_adjustment,
289 coupon->dayCounter(), coupon->accrualStartDate(), coupon->accrualEndDate(),
290 coupon->referencePeriodStart(), coupon->referencePeriodEnd());
291
292 fixedCFS.push_back(newCpn);
293 }
294
295
296 // this is the irregular swap with spread removed
297 swap_ = ext::make_shared<IrregularSwap>(arguments_.swap->type(), fixedCFS, floatCFS);
298
299
300 // Sets up the basket by implementing the methodology described in
301 // P.S.Hagan "Callable Swaps and Bermudan 'Exercise into Swaptions'"
303
304
306 // find lambda //
308
309 Bisection s1d;
310
311 Rate minLambda = -0.5;
312 Rate maxLambda = 0.5;
313 s1d.setMaxEvaluations(10000);
314 s1d.setLowerBound(minLambda);
315 s1d.setUpperBound(maxLambda);
316 s1d.solve(basket, 1.0e-8, 0.01, minLambda, maxLambda);
317
318
320 // compute the price of the irreg swaption as the sum of the prices of the regular
321 // swaptions //
323
324
325 results_.value = HKPrice(basket, exercise_);
326 }
327
328
330 // Computes irregular swaption price according to P.J. Hunt, J.E. Kennedy: //
331 // "Implied interest rate pricing models", Finance Stochast. 2, 275-293 (1998) //
333
335 ext::shared_ptr<Exercise>& exercise) const {
336
337 // Swaption Engine: assumes that the swaptions exercise date equals the swap start date
338 QL_REQUIRE((volatilityStructure_->volatilityType() == Normal),
339 "swaptionEngine: only normal volatility implemented.");
340
341
342 ext::shared_ptr<PricingEngine> swaptionEngine = ext::shared_ptr<PricingEngine>(
344
345
346 // retrieve weights of underlying swaps
347 Array weights = basket.weights();
348
349 Real npv = 0.0;
350
351 for (Size i = 0; i < weights.size(); ++i) {
352 ext::shared_ptr<VanillaSwap> pvSwap_ = basket.component(i);
353 Swaption swaption = Swaption(pvSwap_, exercise);
354 swaption.setPricingEngine(swaptionEngine);
355 npv += weights[i] * swaption.NPV();
356 }
357
358 return npv;
359 }
360
361
362}
1-D array used in linear algebra.
Definition: array.hpp:52
Size size() const
dimension of the array
Definition: array.hpp:495
Normal Bachelier-formula swaption engine.
Bisection 1-D solver
Definition: bisection.hpp:37
static Real npv(const Leg &leg, const YieldTermStructure &discountCurve, bool includeSettlementDateFlows, Date settlementDate=Date(), Date npvDate=Date())
NPV of the cash flows.
Definition: cashflows.cpp:425
static Real bps(const Leg &leg, const YieldTermStructure &discountCurve, bool includeSettlementDateFlows, Date settlementDate=Date(), Date npvDate=Date())
Basis-point sensitivity of the cash flows.
Definition: cashflows.cpp:450
Basket(ext::shared_ptr< IrregularSwap > swap, Handle< YieldTermStructure > termStructure, Handle< SwaptionVolatilityStructure > volatilityStructure)
ext::shared_ptr< VanillaSwap > component(Size i) const
Real HKPrice(Basket &basket, ext::shared_ptr< Exercise > &exercise) const
Handle< SwaptionVolatilityStructure > volatilityStructure_
HaganIrregularSwaptionEngine(Handle< SwaptionVolatilityStructure >, Handle< YieldTermStructure > termStructure=Handle< YieldTermStructure >())
Shared handle to an observable.
Definition: handle.hpp:41
Real NPV() const
returns the net present value of the instrument.
Definition: instrument.hpp:167
void setPricingEngine(const ext::shared_ptr< PricingEngine > &)
set the pricing engine to be used.
Definition: instrument.cpp:35
ext::shared_ptr< IrregularSwap > swap
MakeVanillaSwap & withEffectiveDate(const Date &)
MakeVanillaSwap & withTerminationDate(const Date &)
MakeVanillaSwap & withDiscountingTermStructure(const Handle< YieldTermStructure > &discountCurve)
MakeVanillaSwap & withRule(DateGeneration::Rule r)
MakeVanillaSwap & withType(Swap::Type type)
Matrix used in linear algebra.
Definition: matrix.hpp:41
std::pair< iterator, bool > registerWith(const ext::shared_ptr< Observable > &)
Definition: observable.hpp:228
ext::shared_ptr< Exercise > exercise
Definition: option.hpp:65
Singular value decomposition.
Definition: svd.hpp:54
Array solveFor(const Array &) const
Definition: svd.cpp:528
void setMaxEvaluations(Size evaluations)
Definition: solver1d.hpp:238
void setLowerBound(Real lowerBound)
sets the lower bound for the function domain
Definition: solver1d.hpp:243
Real solve(const F &f, Real accuracy, Real guess, Real step) const
Definition: solver1d.hpp:84
void setUpperBound(Real upperBound)
sets the upper bound for the function domain
Definition: solver1d.hpp:249
Swaption class
Definition: swaption.hpp:81
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
void swap(Array &v, Array &w) noexcept
Definition: array.hpp:903
std::vector< ext::shared_ptr< CashFlow > > Leg
Sequence of cash-flows.
Definition: cashflow.hpp:78
STL namespace.