Skip to content

Commit

Permalink
A few fixes for callable bonds (#1485)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio committed Sep 21, 2022
2 parents 03f0a93 + 6f78f63 commit a57eb97
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 168 deletions.
4 changes: 2 additions & 2 deletions ql/experimental/callablebonds/blackcallablebondengine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ namespace QuantLib {
Duration::Modified,
false, exerciseDate);

Real cashStrike = arguments_.callabilityPrices[0];
Real cashStrike = arguments_.callabilityPrices[0] * arguments_.faceAmount / 100.0;
dayCounter = volatility_->dayCounter();
Date referenceDate = volatility_->referenceDate();
Time exerciseTime = dayCounter.yearFraction(referenceDate,
Expand Down Expand Up @@ -145,7 +145,7 @@ namespace QuantLib {
Real fwdCashPrice = (value - spotIncome())/
discountCurve_->discount(exerciseDate);

Real cashStrike = arguments_.callabilityPrices[0];
Real cashStrike = arguments_.callabilityPrices[0] * arguments_.faceAmount / 100.0;

Option::Type type = (arguments_.putCallSchedule[0]->type() ==
Callability::Call ? Option::Call : Option::Put);
Expand Down
257 changes: 147 additions & 110 deletions ql/experimental/callablebonds/callablebond.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@
namespace QuantLib {

CallableBond::CallableBond(Natural settlementDays,
const Schedule& schedule,
const Date& maturityDate,
const Calendar& calendar,
DayCounter paymentDayCounter,
Real faceAmount,
const Date& issueDate,
CallabilitySchedule putCallSchedule)
: Bond(settlementDays, schedule.calendar(), issueDate),
: Bond(settlementDays, calendar, issueDate),
paymentDayCounter_(std::move(paymentDayCounter)),
putCallSchedule_(std::move(putCallSchedule)) {
putCallSchedule_(std::move(putCallSchedule)), faceAmount_(faceAmount) {

maturityDate_ = schedule.dates().back();
maturityDate_ = maturityDate;

if (!putCallSchedule_.empty()) {
Date finalOptionDate = Date::minDate();
Expand Down Expand Up @@ -68,28 +70,72 @@ namespace QuantLib {
}


class CallableBond::ImpliedVolHelper {
public:
ImpliedVolHelper(const CallableBond& bond,
const Handle<YieldTermStructure>& discountCurve,
Real targetValue,
bool matchNPV);
Real operator()(Volatility x) const;
private:
ext::shared_ptr<PricingEngine> engine_;
Real targetValue_;
bool matchNPV_;
ext::shared_ptr<SimpleQuote> vol_;
const CallableBond::results* results_;
};

CallableBond::ImpliedVolHelper::ImpliedVolHelper(
const CallableBond& bond,
Real targetValue)
: targetValue_(targetValue) {
const Handle<YieldTermStructure>& discountCurve,
Real targetValue,
bool matchNPV)
: targetValue_(targetValue), matchNPV_(matchNPV) {

vol_ = ext::make_shared<SimpleQuote>(0.0);
bond.blackVolQuote_.linkTo(vol_);

QL_REQUIRE(bond.blackEngine_,
"Must set blackEngine_ to use impliedVolatility");
engine_ = ext::make_shared<BlackCallableFixedRateBondEngine>(Handle<Quote>(vol_),
discountCurve);

engine_ = bond.blackEngine_;
bond.setupArguments(engine_->getArguments());
results_ =
dynamic_cast<const Instrument::results*>(engine_->getResults());
dynamic_cast<const CallableBond::results*>(engine_->getResults());
}


Real CallableBond::ImpliedVolHelper::operator()(Volatility x) const {
vol_->setValue(x);
engine_->calculate(); // get the Black NPV based on vol x
return results_->value-targetValue_;
Real value = matchNPV_ ? results_->value : results_->settlementValue;
return value - targetValue_;
}


Volatility CallableBond::impliedVolatility(
const Bond::Price& targetPrice,
const Handle<YieldTermStructure>& discountCurve,
Real accuracy,
Size maxEvaluations,
Volatility minVol,
Volatility maxVol) const {
QL_REQUIRE(!isExpired(), "instrument expired");

Real dirtyTargetPrice;
switch (targetPrice.type()) {
case Bond::Price::Dirty:
dirtyTargetPrice = targetPrice.amount();
break;
case Bond::Price::Clean:
dirtyTargetPrice = targetPrice.amount() + accruedAmount();
break;
default:
QL_FAIL("unknown price type");
}

Real targetValue = dirtyTargetPrice * faceAmount_ / 100.0;
Volatility guess = 0.5 * (minVol + maxVol);
ImpliedVolHelper f(*this, discountCurve, targetValue, false);
Brent solver;
solver.setMaxEvaluations(maxEvaluations);
return solver.solve(f, accuracy, guess, minVol, maxVol);
}

Volatility CallableBond::impliedVolatility(
Expand All @@ -99,16 +145,15 @@ namespace QuantLib {
Size maxEvaluations,
Volatility minVol,
Volatility maxVol) const {
calculate();
QL_REQUIRE(!isExpired(), "instrument expired");
Volatility guess = 0.5*(minVol + maxVol);
blackDiscountCurve_.linkTo(*discountCurve, false);
ImpliedVolHelper f(*this,targetValue);
Volatility guess = 0.5 * (minVol + maxVol);
ImpliedVolHelper f(*this, discountCurve, targetValue, true);
Brent solver;
solver.setMaxEvaluations(maxEvaluations);
return solver.solve(f, accuracy, guess, minVol, maxVol);
}


namespace {

template<class T>
Expand Down Expand Up @@ -220,22 +265,31 @@ namespace QuantLib {
}


class CallableBond::NPVSpreadHelper {
public:
explicit NPVSpreadHelper(CallableBond& bond);
Real operator()(Spread x) const;
private:
CallableBond& bond_;
const Instrument::results* results_;
};

CallableBond::NPVSpreadHelper::NPVSpreadHelper(CallableBond& bond):
bond_(bond),
results_(dynamic_cast<const Instrument::results*>(bond.engine_->getResults()))
{
bond.setupArguments(bond.engine_->getArguments());
}

Real CallableBond::NPVSpreadHelper::operator()(Real x) const
{
auto* args = dynamic_cast<CallableBond::arguments*>(bond_.engine_->getArguments());
// Pops the original value when function finishes
RestoreVal<Spread> restorer(args->spread);
args->spread=x;
bond_.engine_->calculate();
return results_->value;
}
Real CallableBond::NPVSpreadHelper::operator()(Real x) const
{
auto* args = dynamic_cast<CallableBond::arguments*>(bond_.engine_->getArguments());
// Pops the original value when function finishes
RestoreVal<Spread> restorer(args->spread);
args->spread=x;
bond_.engine_->calculate();
return results_->value;
}

Spread CallableBond::OAS(Real cleanPrice,
const Handle<YieldTermStructure>& engineTS,
Expand Down Expand Up @@ -361,86 +415,17 @@ namespace QuantLib {
}


CallableFixedRateBond::CallableFixedRateBond(
Natural settlementDays,
Real faceAmount,
const Schedule& schedule,
const std::vector<Rate>& coupons,
const DayCounter& accrualDayCounter,
BusinessDayConvention paymentConvention,
Real redemption,
const Date& issueDate,
const CallabilitySchedule& putCallSchedule,
const Period& exCouponPeriod,
const Calendar& exCouponCalendar,
BusinessDayConvention exCouponConvention,
bool exCouponEndOfMonth)
: CallableBond(settlementDays, schedule, accrualDayCounter,
issueDate, putCallSchedule) {

frequency_ = schedule.tenor().frequency();

bool isZeroCouponBond = (coupons.size() == 1 && close(coupons[0], 0.0));

if (!isZeroCouponBond) {
cashflows_ =
FixedRateLeg(schedule)
.withNotionals(faceAmount)
.withCouponRates(coupons, accrualDayCounter)
.withPaymentAdjustment(paymentConvention)
.withExCouponPeriod(exCouponPeriod,
exCouponCalendar,
exCouponConvention,
exCouponEndOfMonth);

addRedemptionsToCashflows(std::vector<Real>(1, redemption));
} else {
Date redemptionDate = calendar_.adjust(maturityDate_,
paymentConvention);
setSingleRedemption(faceAmount, redemption, redemptionDate);
}

// used for impliedVolatility() calculation
ext::shared_ptr<SimpleQuote> dummyVolQuote(new SimpleQuote(0.));
blackVolQuote_.linkTo(dummyVolQuote);
blackEngine_ = ext::shared_ptr<PricingEngine>(
new BlackCallableFixedRateBondEngine(blackVolQuote_,
blackDiscountCurve_));
}


Real CallableFixedRateBond::accrued(Date settlement) const {
void CallableBond::setupArguments(PricingEngine::arguments* args) const {

if (settlement == Date()) settlement = settlementDate();

const bool IncludeToday = false;
for (const auto& cashflow : cashflows_) {
// the first coupon paying after d is the one we're after
if (!cashflow->hasOccurred(settlement, IncludeToday)) {
ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow);
if (coupon != nullptr)
// !!!
return coupon->accruedAmount(settlement) /
notional(settlement) * 100.0;
else
return 0.0;
}
}
return 0.0;
}


void CallableFixedRateBond::setupArguments(
PricingEngine::arguments* args) const {

CallableBond::setupArguments(args);
Bond::setupArguments(args);

auto* arguments = dynamic_cast<CallableBond::arguments*>(args);

QL_REQUIRE(arguments != nullptr, "no arguments given");

Date settlement = arguments->settlementDate;

arguments->faceAmount = faceAmount_;
arguments->redemption = redemption()->amount();
arguments->redemptionDate = redemption()->date();

Expand Down Expand Up @@ -488,6 +473,60 @@ namespace QuantLib {
}


Real CallableBond::accrued(Date settlement) const {

if (settlement == Date()) settlement = settlementDate();

const bool IncludeToday = false;
for (const auto& cashflow : cashflows_) {
// the first coupon paying after d is the one we're after
if (!cashflow->hasOccurred(settlement, IncludeToday)) {
ext::shared_ptr<Coupon> coupon = ext::dynamic_pointer_cast<Coupon>(cashflow);
if (coupon != nullptr)
// !!!
return coupon->accruedAmount(settlement) /
notional(settlement) * 100.0;
else
return 0.0;
}
}
return 0.0;
}


CallableFixedRateBond::CallableFixedRateBond(
Natural settlementDays,
Real faceAmount,
const Schedule& schedule,
const std::vector<Rate>& coupons,
const DayCounter& accrualDayCounter,
BusinessDayConvention paymentConvention,
Real redemption,
const Date& issueDate,
const CallabilitySchedule& putCallSchedule,
const Period& exCouponPeriod,
const Calendar& exCouponCalendar,
BusinessDayConvention exCouponConvention,
bool exCouponEndOfMonth)
: CallableBond(settlementDays, schedule.dates().back(), schedule.calendar(),
accrualDayCounter, faceAmount, issueDate, putCallSchedule) {

frequency_ = schedule.tenor().frequency();

cashflows_ =
FixedRateLeg(schedule)
.withNotionals(faceAmount)
.withCouponRates(coupons, accrualDayCounter)
.withPaymentAdjustment(paymentConvention)
.withExCouponPeriod(exCouponPeriod,
exCouponCalendar,
exCouponConvention,
exCouponEndOfMonth);

addRedemptionsToCashflows({redemption});
}


CallableZeroCouponBond::CallableZeroCouponBond(
Natural settlementDays,
Real faceAmount,
Expand All @@ -498,17 +537,15 @@ namespace QuantLib {
Real redemption,
const Date& issueDate,
const CallabilitySchedule& putCallSchedule)
: CallableFixedRateBond(settlementDays,faceAmount,
Schedule(issueDate, maturityDate,
Period(Once),
calendar,
paymentConvention,
paymentConvention,
DateGeneration::Backward,
false),
std::vector<Rate>(1, 0.0), dayCounter,
paymentConvention, redemption,
issueDate, putCallSchedule) {}
: CallableBond(settlementDays, maturityDate, calendar,
dayCounter, faceAmount, issueDate, putCallSchedule) {

frequency_ = Once;

Date redemptionDate = calendar_.adjust(maturityDate_,
paymentConvention);
setSingleRedemption(faceAmount, redemption, redemptionDate);
}

}

Loading

0 comments on commit a57eb97

Please sign in to comment.