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
30 CallableBond::CallableBond(Natural settlementDays,
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
41 maturityDate_ = maturityDate;
42
43 if (!putCallSchedule_.empty()) {
44 Date finalOptionDate = Date::minDate();
45 for (auto& i : putCallSchedule_) {
46 finalOptionDate = std::max(a: finalOptionDate, b: 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
56 void CallableBond::arguments::validate() const {
57
58 QL_REQUIRE(Bond::arguments::settlementDate != Date(),
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>(args: 0.0);
96 engine_ = ext::make_shared<BlackCallableFixedRateBondEngine>(args: Handle<Quote>(vol_),
97 args: 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
112 Volatility CallableBond::impliedVolatility(
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()) {
123 case Bond::Price::Dirty:
124 dirtyTargetPrice = targetPrice.amount();
125 break;
126 case Bond::Price::Clean:
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, xMin: minVol, xMax: maxVol);
139 }
140
141 Volatility CallableBond::impliedVolatility(
142 Real targetValue,
143 const Handle<YieldTermStructure>& discountCurve,
144 Real accuracy,
145 Size maxEvaluations,
146 Volatility minVol,
147 Volatility maxVol) const {
148 QL_REQUIRE(!isExpired(), "instrument expired");
149 Volatility guess = 0.5 * (minVol + maxVol);
150 ImpliedVolHelper f(*this, discountCurve, targetValue, true);
151 Brent solver;
152 solver.setMaxEvaluations(maxEvaluations);
153 return solver.solve(f, accuracy, guess, xMin: minVol, xMax: maxVol);
154 }
155
156
157 namespace {
158
159 template<class T>
160 class RestoreVal { // NOLINT(cppcoreguidelines-special-member-functions)
161 T orig_;
162 T &ref_;
163 public:
164 explicit RestoreVal(T &ref):
165 orig_(ref),
166 ref_(ref) { }
167 ~RestoreVal()
168 {
169 ref_=orig_;
170 }
171 };
172
173 class OASHelper {
174 public:
175 OASHelper(const ext::function<Real(Real)>& npvhelper,
176 Real targetValue):
177 npvhelper_(npvhelper),
178 targetValue_(targetValue)
179 {
180 }
181
182 Real operator()(Spread x) const
183 {
184 return targetValue_ - npvhelper_(x);
185 }
186 private:
187 const ext::function<Real(Real)>& npvhelper_;
188 Real targetValue_;
189 };
190
191
192 /* Convert a continuous spread to a conventional spread to a
193 reference yield curve
194 */
195 Real continuousToConv(Real oas,
196 const Bond &b,
197 const Handle<YieldTermStructure>& yts,
198 const DayCounter& dayCounter,
199 Compounding compounding,
200 Frequency frequency)
201 {
202 Real zz=yts->zeroRate(d: b.maturityDate(),
203 resultDayCounter: dayCounter,
204 comp: Continuous,
205 freq: NoFrequency);
206 InterestRate baseRate(zz,
207 dayCounter,
208 Continuous,
209 NoFrequency);
210 InterestRate spreadedRate(oas+zz,
211 dayCounter,
212 Continuous,
213 NoFrequency);
214 Real br=baseRate.equivalentRate(resultDC: dayCounter,
215 comp: compounding,
216 freq: frequency,
217 d1: yts->referenceDate(),
218 d2: b.maturityDate()).rate();
219 Real sr=spreadedRate.equivalentRate(resultDC: dayCounter,
220 comp: compounding,
221 freq: frequency,
222 d1: yts->referenceDate(),
223 d2: b.maturityDate()).rate();
224 // Return the spread
225 return sr-br;
226 }
227
228 /* Convert a conventional spread to a reference yield curve to a
229 continuous spread
230 */
231 Real convToContinuous(Real oas,
232 const Bond &b,
233 const Handle<YieldTermStructure>& yts,
234 const DayCounter& dayCounter,
235 Compounding compounding,
236 Frequency frequency)
237 {
238 Real zz=yts->zeroRate(d: b.maturityDate(),
239 resultDayCounter: dayCounter,
240 comp: compounding,
241 freq: frequency);
242 InterestRate baseRate(zz,
243 dayCounter,
244 compounding,
245 frequency);
246
247 InterestRate spreadedRate(oas+zz,
248 dayCounter,
249 compounding,
250 frequency);
251 Real br=baseRate.equivalentRate(resultDC: dayCounter,
252 comp: Continuous,
253 freq: NoFrequency,
254 d1: yts->referenceDate(),
255 d2: b.maturityDate()).rate();
256 Real sr=spreadedRate.equivalentRate(resultDC: dayCounter,
257 comp: Continuous,
258 freq: NoFrequency,
259 d1: yts->referenceDate(),
260 d2: b.maturityDate()).rate();
261 // Return the spread
262 return sr-br;
263 }
264
265 }
266
267
268 class CallableBond::NPVSpreadHelper {
269 public:
270 explicit NPVSpreadHelper(CallableBond& bond);
271 Real operator()(Spread x) const;
272 private:
273 CallableBond& bond_;
274 const Instrument::results* results_;
275 };
276
277 CallableBond::NPVSpreadHelper::NPVSpreadHelper(CallableBond& bond):
278 bond_(bond),
279 results_(dynamic_cast<const Instrument::results*>(bond.engine_->getResults()))
280 {
281 bond.setupArguments(bond.engine_->getArguments());
282 }
283
284 Real CallableBond::NPVSpreadHelper::operator()(Real x) const
285 {
286 auto* args = dynamic_cast<CallableBond::arguments*>(bond_.engine_->getArguments());
287 // Pops the original value when function finishes
288 RestoreVal<Spread> restorer(args->spread);
289 args->spread=x;
290 bond_.engine_->calculate();
291 return results_->value;
292 }
293
294 Spread CallableBond::OAS(Real cleanPrice,
295 const Handle<YieldTermStructure>& engineTS,
296 const DayCounter& dayCounter,
297 Compounding compounding,
298 Frequency frequency,
299 Date settlement,
300 Real accuracy,
301 Size maxIterations,
302 Spread guess)
303 {
304 if (settlement == Date())
305 settlement = settlementDate();
306
307 Real dirtyPrice = cleanPrice + accruedAmount(d: settlement);
308
309 ext::function<Real(Real)> f = NPVSpreadHelper(*this);
310 OASHelper obj(f, dirtyPrice);
311
312 Brent solver;
313 solver.setMaxEvaluations(maxIterations);
314
315 Real step = 0.001;
316 Spread oas=solver.solve(f: obj, accuracy, guess, step);
317
318 return continuousToConv(oas,
319 b: *this,
320 yts: engineTS,
321 dayCounter,
322 compounding,
323 frequency);
324 }
325
326
327
328 Real CallableBond::cleanPriceOAS(Real oas,
329 const Handle<YieldTermStructure>& engineTS,
330 const DayCounter& dayCounter,
331 Compounding compounding,
332 Frequency frequency,
333 Date settlement)
334 {
335 if (settlement == Date())
336 settlement = settlementDate();
337
338 oas=convToContinuous(oas,
339 b: *this,
340 yts: engineTS,
341 dayCounter,
342 compounding,
343 frequency);
344
345 ext::function<Real(Real)> f = NPVSpreadHelper(*this);
346
347 Real P = f(oas) - accruedAmount(d: settlement);
348
349 return P;
350 }
351
352 Real CallableBond::effectiveDuration(Real oas,
353 const Handle<YieldTermStructure>& engineTS,
354 const DayCounter& dayCounter,
355 Compounding compounding,
356 Frequency frequency,
357 Real bump)
358 {
359 Real P = cleanPriceOAS(oas,
360 engineTS,
361 dayCounter,
362 compounding,
363 frequency);
364
365 Real Ppp = cleanPriceOAS(oas: oas+bump,
366 engineTS,
367 dayCounter,
368 compounding,
369 frequency);
370 Real Pmm = cleanPriceOAS(oas: oas-bump,
371 engineTS,
372 dayCounter,
373 compounding,
374 frequency);
375
376 if ( P == 0.0 )
377 return 0;
378 else
379 {
380 return (Pmm-Ppp)/(2*P*bump);
381 }
382 }
383
384 Real CallableBond::effectiveConvexity(Real oas,
385 const Handle<YieldTermStructure>& engineTS,
386 const DayCounter& dayCounter,
387 Compounding compounding,
388 Frequency frequency,
389 Real bump)
390 {
391 Real P = cleanPriceOAS(oas,
392 engineTS,
393 dayCounter,
394 compounding,
395 frequency);
396
397 Real Ppp = cleanPriceOAS(oas: oas+bump,
398 engineTS,
399 dayCounter,
400 compounding,
401 frequency);
402 Real Pmm = cleanPriceOAS(oas: oas-bump,
403 engineTS,
404 dayCounter,
405 compounding,
406 frequency);
407
408 if ( P == 0.0 )
409 return 0;
410 else
411 {
412 return (Ppp + Pmm - 2*P) / ( std::pow(x: bump,y: 2) * P);
413 }
414
415 }
416
417
418 void CallableBond::setupArguments(PricingEngine::arguments* args) const {
419
420 Bond::setupArguments(args);
421
422 auto* arguments = dynamic_cast<CallableBond::arguments*>(args);
423
424 QL_REQUIRE(arguments != nullptr, "no arguments given");
425
426 Date settlement = arguments->settlementDate;
427
428 arguments->faceAmount = faceAmount_;
429 arguments->redemption = redemption()->amount();
430 arguments->redemptionDate = redemption()->date();
431
432 const Leg& cfs = cashflows();
433
434 arguments->couponDates.clear();
435 arguments->couponDates.reserve(n: cfs.size()-1);
436 arguments->couponAmounts.clear();
437 arguments->couponAmounts.reserve(n: cfs.size()-1);
438
439 for (Size i=0; i<cfs.size()-1; i++) {
440 if (!cfs[i]->hasOccurred(refDate: settlement, includeRefDate: false)
441 && !cfs[i]->tradingExCoupon(refDate: settlement)) {
442 arguments->couponDates.push_back(x: cfs[i]->date());
443 arguments->couponAmounts.push_back(x: cfs[i]->amount());
444 }
445 }
446
447 arguments->callabilityPrices.clear();
448 arguments->callabilityDates.clear();
449 arguments->callabilityPrices.reserve(n: putCallSchedule_.size());
450 arguments->callabilityDates.reserve(n: putCallSchedule_.size());
451
452 arguments->paymentDayCounter = paymentDayCounter_;
453 arguments->frequency = frequency_;
454
455 arguments->putCallSchedule = putCallSchedule_;
456 for (const auto& i : putCallSchedule_) {
457 if (!i->hasOccurred(refDate: settlement, includeRefDate: false)) {
458 arguments->callabilityDates.push_back(x: i->date());
459 arguments->callabilityPrices.push_back(x: i->price().amount());
460
461 if (i->price().type() == Bond::Price::Clean) {
462 /* calling accrued() forces accrued interest to be zero
463 if future option date is also coupon date, so that dirty
464 price = clean price. Use here because callability is
465 always applied before coupon in the tree engine.
466 */
467 arguments->callabilityPrices.back() += this->accrued(settlement: i->date());
468 }
469 }
470 }
471
472 arguments->spread = 0.0;
473 }
474
475
476 Real CallableBond::accrued(Date settlement) const {
477
478 if (settlement == Date()) settlement = settlementDate();
479
480 const bool IncludeToday = false;
481 for (const auto& cashflow : cashflows_) {
482 // the first coupon paying after d is the one we're after
483 if (!cashflow->hasOccurred(refDate: settlement, includeRefDate: IncludeToday)) {
484 ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(r: cashflow);
485 if (coupon != nullptr)
486 // !!!
487 return coupon->accruedAmount(settlement) /
488 notional(d: settlement) * 100.0;
489 else
490 return 0.0;
491 }
492 }
493 return 0.0;
494 }
495
496
497 CallableFixedRateBond::CallableFixedRateBond(
498 Natural settlementDays,
499 Real faceAmount,
500 const Schedule& schedule,
501 const std::vector<Rate>& coupons,
502 const DayCounter& accrualDayCounter,
503 BusinessDayConvention paymentConvention,
504 Real redemption,
505 const Date& issueDate,
506 const CallabilitySchedule& putCallSchedule,
507 const Period& exCouponPeriod,
508 const Calendar& exCouponCalendar,
509 BusinessDayConvention exCouponConvention,
510 bool exCouponEndOfMonth)
511 : CallableBond(settlementDays, schedule.dates().back(), schedule.calendar(),
512 accrualDayCounter, faceAmount, issueDate, putCallSchedule) {
513
514 frequency_ = schedule.hasTenor() ? schedule.tenor().frequency() : NoFrequency;
515
516 cashflows_ =
517 FixedRateLeg(schedule)
518 .withNotionals(faceAmount)
519 .withCouponRates(coupons, paymentDayCounter: accrualDayCounter)
520 .withPaymentAdjustment(paymentConvention)
521 .withExCouponPeriod(exCouponPeriod,
522 exCouponCalendar,
523 exCouponConvention,
524 endOfMonth: exCouponEndOfMonth);
525
526 addRedemptionsToCashflows(redemptions: {redemption});
527 }
528
529
530 CallableZeroCouponBond::CallableZeroCouponBond(
531 Natural settlementDays,
532 Real faceAmount,
533 const Calendar& calendar,
534 const Date& maturityDate,
535 const DayCounter& dayCounter,
536 BusinessDayConvention paymentConvention,
537 Real redemption,
538 const Date& issueDate,
539 const CallabilitySchedule& putCallSchedule)
540 : CallableBond(settlementDays, maturityDate, calendar,
541 dayCounter, faceAmount, issueDate, putCallSchedule) {
542
543 frequency_ = Once;
544
545 Date redemptionDate = calendar_.adjust(maturityDate_,
546 convention: paymentConvention);
547 setSingleRedemption(notional: faceAmount, redemption, date: redemptionDate);
548 }
549
550}
551
552

source code of quantlib/ql/experimental/callablebonds/callablebond.cpp