QuantLib: a free/open-source library for quantitative finance
Fully annotated sources - version 1.32
Loading...
Searching...
No Matches
callablebond.cpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3/*
4 Copyright (C) 2008 Allen Kuo
5 Copyright (C) 2017 BN Algorithms Ltd
6
7 This file is part of QuantLib, a free-software/open-source library
8 for financial quantitative analysts and developers - http://quantlib.org/
9
10 QuantLib is free software: you can redistribute it and/or modify it
11 under the terms of the QuantLib license. You should have received a
12 copy of the license along with this program; if not, please email
13 <quantlib-dev@lists.sf.net>. The license is also available online at
14 <http://quantlib.org/license.shtml>.
15
16 This program is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18 FOR A PARTICULAR PURPOSE. See the license for more details.
19*/
20
21#include <ql/cashflows/cashflowvectors.hpp>
22#include <ql/experimental/callablebonds/blackcallablebondengine.hpp>
23#include <ql/experimental/callablebonds/callablebond.hpp>
24#include <ql/math/solvers1d/brent.hpp>
25#include <ql/termstructures/yield/zerospreadedtermstructure.hpp>
26#include <utility>
27
28namespace QuantLib {
29
31 const Date& maturityDate,
32 const Calendar& calendar,
33 DayCounter paymentDayCounter,
34 Real faceAmount,
35 const Date& issueDate,
36 CallabilitySchedule putCallSchedule)
37 : Bond(settlementDays, calendar, issueDate),
38 paymentDayCounter_(std::move(paymentDayCounter)),
39 putCallSchedule_(std::move(putCallSchedule)), faceAmount_(faceAmount) {
40
42
43 if (!putCallSchedule_.empty()) {
44 Date finalOptionDate = Date::minDate();
45 for (auto& i : putCallSchedule_) {
46 finalOptionDate = std::max(finalOptionDate, i->date());
47 }
48 QL_REQUIRE(finalOptionDate <= maturityDate_ ,
49 "Bond cannot mature before last call/put date");
50 }
51
52 // derived classes must set cashflows_ and frequency_
53 }
54
55
57
59 "null settlement date");
60
61 QL_REQUIRE(redemption != Null<Real>(), "null redemption");
62 QL_REQUIRE(redemption >= 0.0,
63 "positive redemption required: "
64 << redemption << " not allowed");
65
66 QL_REQUIRE(callabilityDates.size() == callabilityPrices.size(),
67 "different number of callability dates and prices");
68 QL_REQUIRE(couponDates.size() == couponAmounts.size(),
69 "different number of coupon dates and amounts");
70 }
71
72
73 class CallableBond::ImpliedVolHelper {
74 public:
75 ImpliedVolHelper(const CallableBond& bond,
76 const Handle<YieldTermStructure>& discountCurve,
77 Real targetValue,
78 bool matchNPV);
79 Real operator()(Volatility x) const;
80 private:
81 ext::shared_ptr<PricingEngine> engine_;
82 Real targetValue_;
83 bool matchNPV_;
84 ext::shared_ptr<SimpleQuote> vol_;
85 const CallableBond::results* results_;
86 };
87
88 CallableBond::ImpliedVolHelper::ImpliedVolHelper(
89 const CallableBond& bond,
90 const Handle<YieldTermStructure>& discountCurve,
91 Real targetValue,
92 bool matchNPV)
93 : targetValue_(targetValue), matchNPV_(matchNPV) {
94
95 vol_ = ext::make_shared<SimpleQuote>(0.0);
96 engine_ = ext::make_shared<BlackCallableFixedRateBondEngine>(Handle<Quote>(vol_),
97 discountCurve);
98
99 bond.setupArguments(engine_->getArguments());
100 results_ =
101 dynamic_cast<const CallableBond::results*>(engine_->getResults());
102 }
103
104 Real CallableBond::ImpliedVolHelper::operator()(Volatility x) const {
105 vol_->setValue(x);
106 engine_->calculate(); // get the Black NPV based on vol x
107 Real value = matchNPV_ ? results_->value : results_->settlementValue;
108 return value - targetValue_;
109 }
110
111
113 const Bond::Price& targetPrice,
114 const Handle<YieldTermStructure>& discountCurve,
115 Real accuracy,
116 Size maxEvaluations,
117 Volatility minVol,
118 Volatility maxVol) const {
119 QL_REQUIRE(!isExpired(), "instrument expired");
120
121 Real dirtyTargetPrice;
122 switch (targetPrice.type()) {
124 dirtyTargetPrice = targetPrice.amount();
125 break;
127 dirtyTargetPrice = targetPrice.amount() + accruedAmount();
128 break;
129 default:
130 QL_FAIL("unknown price type");
131 }
132
133 Real targetValue = dirtyTargetPrice * faceAmount_ / 100.0;
134 Volatility guess = 0.5 * (minVol + maxVol);
135 ImpliedVolHelper f(*this, discountCurve, targetValue, false);
136 Brent solver;
137 solver.setMaxEvaluations(maxEvaluations);
138 return solver.solve(f, accuracy, guess, minVol, maxVol);
139 }
140
141
142 namespace {
143
144 template<class T>
145 class RestoreVal { // NOLINT(cppcoreguidelines-special-member-functions)
146 T orig_;
147 T &ref_;
148 public:
149 explicit RestoreVal(T &ref):
150 orig_(ref),
151 ref_(ref) { }
152 ~RestoreVal()
153 {
154 ref_=orig_;
155 }
156 };
157
158 class OASHelper {
159 public:
160 OASHelper(const ext::function<Real(Real)>& npvhelper,
161 Real targetValue):
162 npvhelper_(npvhelper),
163 targetValue_(targetValue)
164 {
165 }
166
167 Real operator()(Spread x) const
168 {
169 return targetValue_ - npvhelper_(x);
170 }
171 private:
172 const ext::function<Real(Real)>& npvhelper_;
173 Real targetValue_;
174 };
175
176
177 /* Convert a continuous spread to a conventional spread to a
178 reference yield curve
179 */
180 Real continuousToConv(Real oas,
181 const Bond &b,
182 const Handle<YieldTermStructure>& yts,
183 const DayCounter& dayCounter,
184 Compounding compounding,
185 Frequency frequency)
186 {
187 Real zz=yts->zeroRate(b.maturityDate(),
188 dayCounter,
191 InterestRate baseRate(zz,
192 dayCounter,
195 InterestRate spreadedRate(oas+zz,
196 dayCounter,
199 Real br=baseRate.equivalentRate(dayCounter,
200 compounding,
201 frequency,
202 yts->referenceDate(),
203 b.maturityDate()).rate();
204 Real sr=spreadedRate.equivalentRate(dayCounter,
205 compounding,
206 frequency,
207 yts->referenceDate(),
208 b.maturityDate()).rate();
209 // Return the spread
210 return sr-br;
211 }
212
213 /* Convert a conventional spread to a reference yield curve to a
214 continuous spread
215 */
216 Real convToContinuous(Real oas,
217 const Bond &b,
218 const Handle<YieldTermStructure>& yts,
219 const DayCounter& dayCounter,
220 Compounding compounding,
221 Frequency frequency)
222 {
223 Real zz=yts->zeroRate(b.maturityDate(),
224 dayCounter,
225 compounding,
226 frequency);
227 InterestRate baseRate(zz,
228 dayCounter,
229 compounding,
230 frequency);
231
232 InterestRate spreadedRate(oas+zz,
233 dayCounter,
234 compounding,
235 frequency);
236 Real br=baseRate.equivalentRate(dayCounter,
239 yts->referenceDate(),
240 b.maturityDate()).rate();
241 Real sr=spreadedRate.equivalentRate(dayCounter,
244 yts->referenceDate(),
245 b.maturityDate()).rate();
246 // Return the spread
247 return sr-br;
248 }
249
250 }
251
252
253 class CallableBond::NPVSpreadHelper {
254 public:
255 explicit NPVSpreadHelper(CallableBond& bond);
256 Real operator()(Spread x) const;
257 private:
258 CallableBond& bond_;
259 const Instrument::results* results_;
260 };
261
262 CallableBond::NPVSpreadHelper::NPVSpreadHelper(CallableBond& bond):
263 bond_(bond),
264 results_(dynamic_cast<const Instrument::results*>(bond.engine_->getResults()))
265 {
266 bond.setupArguments(bond.engine_->getArguments());
267 }
268
269 Real CallableBond::NPVSpreadHelper::operator()(Real x) const
270 {
271 auto* args = dynamic_cast<CallableBond::arguments*>(bond_.engine_->getArguments());
272 // Pops the original value when function finishes
273 RestoreVal<Spread> restorer(args->spread);
274 args->spread=x;
275 bond_.engine_->calculate();
276 return results_->value;
277 }
278
280 const Handle<YieldTermStructure>& engineTS,
281 const DayCounter& dayCounter,
282 Compounding compounding,
283 Frequency frequency,
284 Date settlement,
285 Real accuracy,
286 Size maxIterations,
287 Spread guess)
288 {
289 if (settlement == Date())
290 settlement = settlementDate();
291
292 Real dirtyPrice = cleanPrice + accruedAmount(settlement);
293
294 ext::function<Real(Real)> f = NPVSpreadHelper(*this);
295 OASHelper obj(f, dirtyPrice);
296
297 Brent solver;
298 solver.setMaxEvaluations(maxIterations);
299
300 Real step = 0.001;
301 Spread oas=solver.solve(obj, accuracy, guess, step);
302
303 return continuousToConv(oas,
304 *this,
305 engineTS,
306 dayCounter,
307 compounding,
308 frequency);
309 }
310
311
312
314 const Handle<YieldTermStructure>& engineTS,
315 const DayCounter& dayCounter,
316 Compounding compounding,
317 Frequency frequency,
318 Date settlement)
319 {
320 if (settlement == Date())
321 settlement = settlementDate();
322
323 oas=convToContinuous(oas,
324 *this,
325 engineTS,
326 dayCounter,
327 compounding,
328 frequency);
329
330 ext::function<Real(Real)> f = NPVSpreadHelper(*this);
331
332 Real P = f(oas) - accruedAmount(settlement);
333
334 return P;
335 }
336
338 const Handle<YieldTermStructure>& engineTS,
339 const DayCounter& dayCounter,
340 Compounding compounding,
341 Frequency frequency,
342 Real bump)
343 {
344 Real P = cleanPriceOAS(oas,
345 engineTS,
346 dayCounter,
347 compounding,
348 frequency);
349
350 Real Ppp = cleanPriceOAS(oas+bump,
351 engineTS,
352 dayCounter,
353 compounding,
354 frequency);
355 Real Pmm = cleanPriceOAS(oas-bump,
356 engineTS,
357 dayCounter,
358 compounding,
359 frequency);
360
361 if ( P == 0.0 )
362 return 0;
363 else
364 {
365 return (Pmm-Ppp)/(2*P*bump);
366 }
367 }
368
370 const Handle<YieldTermStructure>& engineTS,
371 const DayCounter& dayCounter,
372 Compounding compounding,
373 Frequency frequency,
374 Real bump)
375 {
376 Real P = cleanPriceOAS(oas,
377 engineTS,
378 dayCounter,
379 compounding,
380 frequency);
381
382 Real Ppp = cleanPriceOAS(oas+bump,
383 engineTS,
384 dayCounter,
385 compounding,
386 frequency);
387 Real Pmm = cleanPriceOAS(oas-bump,
388 engineTS,
389 dayCounter,
390 compounding,
391 frequency);
392
393 if ( P == 0.0 )
394 return 0;
395 else
396 {
397 return (Ppp + Pmm - 2*P) / ( std::pow(bump,2) * P);
398 }
399
400 }
401
402
404
406
407 auto* arguments = dynamic_cast<CallableBond::arguments*>(args);
408
409 QL_REQUIRE(arguments != nullptr, "no arguments given");
410
411 Date settlement = arguments->settlementDate;
412
414 arguments->redemption = redemption()->amount();
416
417 const Leg& cfs = cashflows();
418
419 arguments->couponDates.clear();
420 arguments->couponDates.reserve(cfs.size()-1);
421 arguments->couponAmounts.clear();
422 arguments->couponAmounts.reserve(cfs.size()-1);
423
424 for (Size i=0; i<cfs.size()-1; i++) {
425 if (!cfs[i]->hasOccurred(settlement, false)
426 && !cfs[i]->tradingExCoupon(settlement)) {
427 arguments->couponDates.push_back(cfs[i]->date());
428 arguments->couponAmounts.push_back(cfs[i]->amount());
429 }
430 }
431
436
439
441 for (const auto& i : putCallSchedule_) {
442 if (!i->hasOccurred(settlement, false)) {
443 arguments->callabilityDates.push_back(i->date());
444 arguments->callabilityPrices.push_back(i->price().amount());
445
446 if (i->price().type() == Bond::Price::Clean) {
447 /* calling accrued() forces accrued interest to be zero
448 if future option date is also coupon date, so that dirty
449 price = clean price. Use here because callability is
450 always applied before coupon in the tree engine.
451 */
452 arguments->callabilityPrices.back() += this->accrued(i->date());
453 }
454 }
455 }
456
457 arguments->spread = 0.0;
458 }
459
460
461 Real CallableBond::accrued(Date settlement) const {
462
463 if (settlement == Date()) settlement = settlementDate();
464
465 const bool IncludeToday = false;
466 for (const auto& cashflow : cashflows_) {
467 // the first coupon paying after d is the one we're after
468 if (!cashflow->hasOccurred(settlement, IncludeToday)) {
469 ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow);
470 if (coupon != nullptr)
471 // !!!
472 return coupon->accruedAmount(settlement) /
473 notional(settlement) * 100.0;
474 else
475 return 0.0;
476 }
477 }
478 return 0.0;
479 }
480
481
483 Natural settlementDays,
484 Real faceAmount,
485 const Schedule& schedule,
486 const std::vector<Rate>& coupons,
487 const DayCounter& accrualDayCounter,
488 BusinessDayConvention paymentConvention,
489 Real redemption,
490 const Date& issueDate,
491 const CallabilitySchedule& putCallSchedule,
492 const Period& exCouponPeriod,
493 const Calendar& exCouponCalendar,
494 BusinessDayConvention exCouponConvention,
495 bool exCouponEndOfMonth)
496 : CallableBond(settlementDays, schedule.dates().back(), schedule.calendar(),
497 accrualDayCounter, faceAmount, issueDate, putCallSchedule) {
498
499 frequency_ = schedule.hasTenor() ? schedule.tenor().frequency() : NoFrequency;
500
501 cashflows_ =
502 FixedRateLeg(schedule)
503 .withNotionals(faceAmount)
504 .withCouponRates(coupons, accrualDayCounter)
505 .withPaymentAdjustment(paymentConvention)
506 .withExCouponPeriod(exCouponPeriod,
507 exCouponCalendar,
508 exCouponConvention,
509 exCouponEndOfMonth);
510
512 }
513
514
516 Natural settlementDays,
517 Real faceAmount,
518 const Calendar& calendar,
519 const Date& maturityDate,
520 const DayCounter& dayCounter,
521 BusinessDayConvention paymentConvention,
522 Real redemption,
523 const Date& issueDate,
524 const CallabilitySchedule& putCallSchedule)
525 : CallableBond(settlementDays, maturityDate, calendar,
526 dayCounter, faceAmount, issueDate, putCallSchedule) {
527
529
530 Date redemptionDate = calendar_.adjust(maturityDate_,
531 paymentConvention);
532 setSingleRedemption(faceAmount, redemption, redemptionDate);
533 }
534
535}
536
Bond price information.
Definition: bond.hpp:62
Type type() const
Definition: bond.hpp:71
Real amount() const
Definition: bond.hpp:67
Base bond class.
Definition: bond.hpp:59
Calendar calendar_
Definition: bond.hpp:285
Real cleanPrice() const
theoretical clean price
Definition: bond.cpp:174
void setupArguments(PricingEngine::arguments *) const override
Definition: bond.cpp:285
bool isExpired() const override
returns whether the instrument might have value greater than zero.
Definition: bond.cpp:103
void addRedemptionsToCashflows(const std::vector< Real > &redemptions=std::vector< Real >())
Definition: bond.cpp:304
virtual Real accruedAmount(Date d=Date()) const
accrued amount at a given date
Definition: bond.cpp:256
const Leg & cashflows() const
Definition: bond.hpp:330
Real dirtyPrice() const
theoretical dirty price
Definition: bond.cpp:178
Leg cashflows_
Definition: bond.hpp:288
const ext::shared_ptr< CashFlow > & redemption() const
Definition: bond.cpp:140
void setSingleRedemption(Real notional, Real redemption, const Date &date)
Definition: bond.cpp:331
Date maturityDate() const
Definition: bond.cpp:150
Date maturityDate_
Definition: bond.hpp:291
virtual Real notional(Date d=Date()) const
Definition: bond.cpp:112
Date settlementDate(Date d=Date()) const
Definition: bond.cpp:161
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
std::vector< Date > callabilityDates
Real redemption
redemption = face amount * redemption / 100.
std::vector< Real > callabilityPrices
bond full/dirty/cash prices
void validate() const override
CallabilitySchedule putCallSchedule
results for a callable bond calculation
Callable bond base class.
CallabilitySchedule putCallSchedule_
Real accrued(Date settlement) const
Real effectiveDuration(Real oas, const Handle< YieldTermStructure > &engineTS, const DayCounter &dayCounter, Compounding compounding, Frequency frequency, Real bump=2e-4)
Spread OAS(Real cleanPrice, const Handle< YieldTermStructure > &engineTS, const DayCounter &dayCounter, Compounding compounding, Frequency frequency, Date settlementDate=Date(), Real accuracy=1.0e-10, Size maxIterations=100, Rate guess=0.0)
Calculate the Option Adjusted Spread (OAS)
Real effectiveConvexity(Real oas, const Handle< YieldTermStructure > &engineTS, const DayCounter &dayCounter, Compounding compounding, Frequency frequency, Real bump=2e-4)
CallableBond(Natural settlementDays, const Date &maturityDate, const Calendar &calendar, DayCounter paymentDayCounter, Real faceAmount, const Date &issueDate=Date(), CallabilitySchedule putCallSchedule=CallabilitySchedule())
void setupArguments(PricingEngine::arguments *args) const override
Real cleanPriceOAS(Real oas, const Handle< YieldTermStructure > &engineTS, const DayCounter &dayCounter, Compounding compounding, Frequency frequency, Date settlementDate=Date())
Volatility impliedVolatility(const Bond::Price &targetPrice, const Handle< YieldTermStructure > &discountCurve, Real accuracy, Size maxEvaluations, Volatility minVol, Volatility maxVol) const
returns the Black implied forward yield volatility
CallableFixedRateBond(Natural settlementDays, Real faceAmount, const Schedule &schedule, const std::vector< Rate > &coupons, const DayCounter &accrualDayCounter, BusinessDayConvention paymentConvention=Following, Real redemption=100.0, const Date &issueDate=Date(), const CallabilitySchedule &putCallSchedule={}, const Period &exCouponPeriod=Period(), const Calendar &exCouponCalendar=Calendar(), BusinessDayConvention exCouponConvention=Unadjusted, bool exCouponEndOfMonth=false)
CallableZeroCouponBond(Natural settlementDays, Real faceAmount, const Calendar &calendar, const Date &maturityDate, const DayCounter &dayCounter, BusinessDayConvention paymentConvention=Following, Real redemption=100.0, const Date &issueDate=Date(), const CallabilitySchedule &putCallSchedule={})
Concrete date class.
Definition: date.hpp:125
static Date minDate()
earliest allowed date
Definition: date.cpp:766
day counter class
Definition: daycounter.hpp:44
helper class building a sequence of fixed rate coupons
FixedRateLeg & withNotionals(Real)
FixedRateLeg & withPaymentAdjustment(BusinessDayConvention)
FixedRateLeg & withCouponRates(Rate, const DayCounter &paymentDayCounter, Compounding comp=Simple, Frequency freq=Annual)
FixedRateLeg & withExCouponPeriod(const Period &, const Calendar &, BusinessDayConvention, bool endOfMonth=false)
Shared handle to an observable.
Definition: handle.hpp:41
ext::shared_ptr< PricingEngine > engine_
Definition: instrument.hpp:110
template class providing a null value for a given type.
Definition: null.hpp:76
Frequency frequency() const
Definition: period.cpp:69
Payment schedule.
Definition: schedule.hpp:40
bool hasTenor() const
Definition: schedule.hpp:186
const Period & tenor() const
Definition: schedule.hpp:190
void setMaxEvaluations(Size evaluations)
Definition: solver1d.hpp:238
Real solve(const F &f, Real accuracy, Real guess, Real step) const
Definition: solver1d.hpp:84
Frequency
Frequency of events.
Definition: frequency.hpp:37
BusinessDayConvention
Business Day conventions.
@ Once
only once, e.g., a zero-coupon
Definition: frequency.hpp:38
@ NoFrequency
null frequency
Definition: frequency.hpp:37
QL_REAL Real
real number
Definition: types.hpp:50
unsigned QL_INTEGER Natural
positive integer
Definition: types.hpp:43
Real Volatility
volatility
Definition: types.hpp:78
Real Spread
spreads on interest rates
Definition: types.hpp:74
std::size_t Size
size of a container
Definition: types.hpp:58
Definition: any.hpp:35
Compounding
Interest rate coumpounding rule.
Definition: compounding.hpp:32
std::vector< ext::shared_ptr< Callability > > CallabilitySchedule
std::vector< ext::shared_ptr< CashFlow > > Leg
Sequence of cash-flows.
Definition: cashflow.hpp:78
STL namespace.