Skip to content

Commit

Permalink
Allow use of risk-free rates in asset swaps (#2118)
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio authored Nov 21, 2024
2 parents 13e24b1 + 4182cd0 commit 9cd5eab
Show file tree
Hide file tree
Showing 3 changed files with 291 additions and 168 deletions.
264 changes: 98 additions & 166 deletions ql/instruments/assetswap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <ql/cashflows/couponpricer.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/iborcoupon.hpp>
#include <ql/cashflows/overnightindexedcoupon.hpp>
#include <ql/cashflows/simplecashflow.hpp>
#include <ql/instruments/assetswap.hpp>
#include <ql/pricingengines/swap/discountingswapengine.hpp>
Expand All @@ -42,39 +43,63 @@ namespace QuantLib {
const DayCounter& floatingDayCounter,
Date dealMaturity,
bool payBondCoupon)
: AssetSwap(payBondCoupon, bond, bondCleanPrice, iborIndex, spread,
Schedule(), floatingDayCounter, parSwap, gearing,
nonParRepayment, dealMaturity) {}

AssetSwap::AssetSwap(bool payBondCoupon,
ext::shared_ptr<Bond> bond,
Real bondCleanPrice,
const ext::shared_ptr<IborIndex>& iborIndex,
Spread spread,
Schedule floatSchedule,
const DayCounter& floatingDayCounter,
bool parSwap,
Real gearing,
Real nonParRepayment,
Date dealMaturity)
: Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice),
nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) {
Schedule tempSch(bond_->settlementDate(),
bond_->maturityDate(),
iborIndex->tenor(),
iborIndex->fixingCalendar(),
iborIndex->businessDayConvention(),
iborIndex->businessDayConvention(),
DateGeneration::Backward,
false); // endOfMonth
if (dealMaturity==Date())
dealMaturity = bond_->maturityDate();
QL_REQUIRE(dealMaturity <= tempSch.dates().back(),

auto overnight = ext::dynamic_pointer_cast<OvernightIndex>(iborIndex);
if (overnight) {
QL_REQUIRE(!floatSchedule.empty(),
"floating schedule is needed when using an overnight index");
}

Schedule schedule = floatSchedule.empty()
? Schedule(bond_->settlementDate(),
bond_->maturityDate(),
iborIndex->tenor(),
iborIndex->fixingCalendar(),
iborIndex->businessDayConvention(),
iborIndex->businessDayConvention(),
DateGeneration::Backward,
false) // endOfMonth
: std::move(floatSchedule);

if (dealMaturity == Date())
dealMaturity = schedule.back();
QL_REQUIRE(dealMaturity <= schedule.back(),
"deal maturity " << dealMaturity <<
" cannot be later than (adjusted) bond maturity " <<
tempSch.dates().back());
QL_REQUIRE(dealMaturity > tempSch.dates()[0],
schedule.back());
QL_REQUIRE(dealMaturity > schedule.front(),
"deal maturity " << dealMaturity <<
" must be later than swap start date " <<
tempSch.dates()[0]);
schedule.front());

// the following might become an input parameter
BusinessDayConvention paymentAdjustment = Following;

Date finalDate = tempSch.calendar().adjust(
Date finalDate = schedule.calendar().adjust(
dealMaturity, paymentAdjustment);
Schedule schedule = tempSch.until(finalDate);
schedule = schedule.until(finalDate);

// bondCleanPrice must be the (forward) clean price
// at the floating schedule start date
upfrontDate_ = schedule.startDate();
Real dirtyPrice = bondCleanPrice_ +
bond_->accruedAmount(upfrontDate_);
Real dirtyPrice = bondCleanPrice_ + bond_->accruedAmount(upfrontDate_);

Real notional = bond_->notional(upfrontDate_);
/* In the market asset swap, the bond is purchased in return for
Expand All @@ -83,182 +108,89 @@ namespace QuantLib {
if (!parSwap_)
notional *= dirtyPrice/100.0;

if (floatingDayCounter==DayCounter())
legs_[1] = IborLeg(schedule, iborIndex)
.withNotionals(notional)
.withPaymentAdjustment(paymentAdjustment)
.withGearings(gearing)
.withSpreads(spread);
else
legs_[1] = IborLeg(schedule, iborIndex)
.withNotionals(notional)
.withPaymentDayCounter(floatingDayCounter)
.withPaymentAdjustment(paymentAdjustment)
.withGearings(gearing)
.withSpreads(spread);

Leg::const_iterator i;
for (i=legs_[1].begin(); i<legs_[1].end(); ++i)
registerWith(*i);
/******** Bond leg ********/

const Leg& bondLeg = bond_->cashflows();
// skip bond redemption
for (i = bondLeg.begin(); i<bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
// whatever might be the choice for the discounting engine
// bond flows on upfrontDate_ must be discarded
bool upfrontDateBondFlows = false;
if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
QL_REQUIRE(!bondLeg.empty(), "no cashflows from bond");

bool includeOnUpfrontDate = false; // a cash flow on the upfront
// date must be discarded

// add coupons for the time being, not the redemption
Leg::const_iterator i;
for (i = bondLeg.begin(); i < bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
if (!(*i)->hasOccurred(upfrontDate_, includeOnUpfrontDate))
legs_[0].push_back(*i);
}
// if the first skipped cashflow is not the redemption
// and it is a coupon then add the accrued coupon
if (i<bondLeg.end()-1) {
ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(*i);

// if we're skipping a cashflow before the redemption
// and it's a coupon, then add the accrued coupon.
if (i < bondLeg.end()-1) {
auto c = ext::dynamic_pointer_cast<Coupon>(*i);
if (c != nullptr) {
ext::shared_ptr<CashFlow> accruedCoupon(new
SimpleCashFlow(c->accruedAmount(dealMaturity), finalDate));
Real accruedAmount = c->accruedAmount(dealMaturity);
auto accruedCoupon =
ext::make_shared<SimpleCashFlow>(accruedAmount, finalDate);
legs_[0].push_back(accruedCoupon);
}
}
// add the nonParRepayment_
ext::shared_ptr<CashFlow> nonParRepaymentFlow(new
SimpleCashFlow(nonParRepayment_, finalDate));
legs_[0].push_back(nonParRepaymentFlow);

QL_REQUIRE(!legs_[0].empty(),
"empty bond leg to start with");

// special flows
if (parSwap_) {
// upfront on the floating leg
Real upfront = (dirtyPrice-100.0)/100.0*notional;
ext::shared_ptr<CashFlow> upfrontCashFlow(new
SimpleCashFlow(upfront, upfrontDate_));
legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
// backpayment on the floating leg
// (accounts for non-par redemption, if any)
Real backPayment = notional;
ext::shared_ptr<CashFlow> backPaymentCashFlow(new
SimpleCashFlow(backPayment, finalDate));
legs_[1].push_back(backPaymentCashFlow);
// add the redemption, or whatever the final payment is
if (nonParRepayment_ == Null<Real>()) {
auto redemption = bondLeg.back();
auto finalFlow =
ext::make_shared<SimpleCashFlow>(redemption->amount(), finalDate);
legs_[0].push_back(finalFlow);
nonParRepayment_ = 100.0;
} else {
// final notional exchange
ext::shared_ptr<CashFlow> finalCashFlow (new
SimpleCashFlow(notional, finalDate));
legs_[1].push_back(finalCashFlow);
auto finalFlow =
ext::make_shared<SimpleCashFlow>(nonParRepayment_, finalDate);
legs_[0].push_back(finalFlow);
}

QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
for (i=legs_[0].begin(); i<legs_[0].end(); ++i)
registerWith(*i);
/******** Floating leg ********/

if (payBondCoupon) {
payer_[0]=-1.0;
payer_[1]=+1.0;
} else {
payer_[0]=+1.0;
payer_[1]=-1.0;
}
}

AssetSwap::AssetSwap(bool payBondCoupon,
ext::shared_ptr<Bond> bond,
Real bondCleanPrice,
const ext::shared_ptr<IborIndex>& iborIndex,
Spread spread,
Schedule floatSchedule,
const DayCounter& floatingDayCounter,
bool parSwap)
: Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice), nonParRepayment_(100),
spread_(spread), parSwap_(parSwap) {
Schedule schedule = floatSchedule.empty()
? Schedule(bond_->settlementDate(),
bond_->maturityDate(),
iborIndex->tenor(),
iborIndex->fixingCalendar(),
iborIndex->businessDayConvention(),
iborIndex->businessDayConvention(),
DateGeneration::Backward,
false) // endOfMonth
: std::move(floatSchedule);

// the following might become an input parameter
BusinessDayConvention paymentAdjustment = Following;

Date finalDate = schedule.calendar().adjust(
schedule.endDate(), paymentAdjustment);
Date adjBondMaturityDate = schedule.calendar().adjust(
bond_->maturityDate(), paymentAdjustment);

QL_REQUIRE(finalDate==adjBondMaturityDate,
"adjusted schedule end date (" <<
finalDate <<
") must be equal to adjusted bond maturity date (" <<
adjBondMaturityDate << ")");

// bondCleanPrice must be the (forward) clean price
// at the floating schedule start date
upfrontDate_ = schedule.startDate();
Real dirtyPrice = bondCleanPrice_ +
bond_->accruedAmount(upfrontDate_);

Real notional = bond_->notional(upfrontDate_);
/* In the market asset swap, the bond is purchased in return for
payment of the full price. The notional of the floating leg is
then scaled by the full price. */
if (!parSwap_)
notional *= dirtyPrice/100.0;

if (floatingDayCounter==DayCounter())
legs_[1] = IborLeg(std::move(schedule), iborIndex)
if (overnight) {
legs_[1] =
OvernightLeg(std::move(schedule), overnight)
.withNotionals(notional)
.withPaymentAdjustment(paymentAdjustment)
.withSpreads(spread);
else
legs_[1] = IborLeg(std::move(schedule), iborIndex)
.withGearings(gearing)
.withSpreads(spread)
.withPaymentDayCounter(floatingDayCounter);
} else {
legs_[1] =
IborLeg(std::move(schedule), iborIndex)
.withNotionals(notional)
.withPaymentDayCounter(floatingDayCounter)
.withPaymentAdjustment(paymentAdjustment)
.withSpreads(spread);

for (auto i=legs_[1].begin(); i<legs_[1].end(); ++i)
registerWith(*i);

const Leg& bondLeg = bond_->cashflows();
for (auto i = bondLeg.begin(); i < bondLeg.end(); ++i) {
// whatever might be the choice for the discounting engine
// bond flows on upfrontDate_ must be discarded
bool upfrontDateBondFlows = false;
if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
legs_[0].push_back(*i);
.withGearings(gearing)
.withSpreads(spread)
.withPaymentDayCounter(floatingDayCounter);
}

QL_REQUIRE(!legs_[0].empty(),
"empty bond leg to start with");

// special flows
if (parSwap_) {
// upfront on the floating leg
Real upfront = (dirtyPrice-100.0)/100.0*notional;
ext::shared_ptr<CashFlow> upfrontCashFlow(new
SimpleCashFlow(upfront, upfrontDate_));
// upfront
Real upfront = (dirtyPrice-100.0)/100.0 * notional;
auto upfrontCashFlow =
ext::make_shared<SimpleCashFlow>(upfront, upfrontDate_);
legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
// backpayment on the floating leg
// (accounts for non-par redemption, if any)
// backpayment (accounts for non-par redemption, if any)
Real backPayment = notional;
ext::shared_ptr<CashFlow> backPaymentCashFlow(new
SimpleCashFlow(backPayment, finalDate));
auto backPaymentCashFlow =
ext::make_shared<SimpleCashFlow>(backPayment, finalDate);
legs_[1].push_back(backPaymentCashFlow);
} else {
// final notional exchange
ext::shared_ptr<CashFlow> finalCashFlow(new
SimpleCashFlow(notional, finalDate));
auto finalCashFlow =
ext::make_shared<SimpleCashFlow>(notional, finalDate);
legs_[1].push_back(finalCashFlow);
}

QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
for (auto i=legs_[0].begin(); i<legs_[0].end(); ++i)
registerWith(*i);
/******** registration and sides ********/

for (const auto& leg: legs_)
for (const auto& c: leg)
registerWith(c);

if (payBondCoupon) {
payer_[0]=-1.0;
Expand Down
17 changes: 15 additions & 2 deletions ql/instruments/assetswap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,27 @@ namespace QuantLib {
class arguments;
class results;

/*! If the passed iborIndex is an overnight rate such as
SOFR, ESTR or SONIA, the floatSchedule argument is
required and will be used to build overnight-indexed
coupons.
*/
AssetSwap(bool payBondCoupon,
ext::shared_ptr<Bond> bond,
Real bondCleanPrice,
const ext::shared_ptr<IborIndex>& iborIndex,
Spread spread,
Schedule floatSchedule = Schedule(),
const DayCounter& floatingDayCount = DayCounter(),
bool parAssetSwap = true);

bool parAssetSwap = true,
Real gearing = 1.0,
Real nonParRepayment = Null<Real>(),
Date dealMaturity = Date());

/*! \deprecated Use the other overload.
Deprecated in version 1.37.
*/
[[deprecated("Use the other overload")]]
AssetSwap(bool parAssetSwap,
ext::shared_ptr<Bond> bond,
Real bondCleanPrice,
Expand All @@ -72,6 +84,7 @@ namespace QuantLib {
const DayCounter& floatingDayCount = DayCounter(),
Date dealMaturity = Date(),
bool payBondCoupon = false);

// results
Spread fairSpread() const;
Real floatingLegBPS() const;
Expand Down
Loading

0 comments on commit 9cd5eab

Please sign in to comment.