Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow use of risk-free rates in asset swaps #2118

Merged
merged 2 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading