Skip to content

Commit

Permalink
Enable using overnight rates in asset swap
Browse files Browse the repository at this point in the history
  • Loading branch information
lballabio committed Nov 21, 2024
1 parent c56549c commit 4182cd0
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 9 deletions.
35 changes: 26 additions & 9 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 Down Expand Up @@ -60,6 +61,12 @@ namespace QuantLib {
: Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice),
nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) {

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(),
Expand Down Expand Up @@ -102,11 +109,11 @@ namespace QuantLib {
notional *= dirtyPrice/100.0;

/******** Bond leg ********/

const Leg& bondLeg = bond_->cashflows();
QL_REQUIRE(!bondLeg.empty(), "no cashflows from bond");

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

// add coupons for the time being, not the redemption
Expand Down Expand Up @@ -143,13 +150,23 @@ namespace QuantLib {

/******** Floating leg ********/

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

if (parSwap_) {
// upfront
Expand Down
5 changes: 5 additions & 0 deletions ql/instruments/assetswap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ 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,
Expand Down
178 changes: 178 additions & 0 deletions test-suite/assetswap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <ql/time/daycounters/actualactual.hpp>
#include <ql/time/daycounters/simpledaycounter.hpp>
#include <ql/indexes/ibor/euribor.hpp>
#include <ql/indexes/ibor/estr.hpp>
#include <ql/indexes/swapindex.hpp>
#include <ql/cashflows/fixedratecoupon.hpp>
#include <ql/cashflows/iborcoupon.hpp>
Expand Down Expand Up @@ -314,11 +315,104 @@ BOOST_AUTO_TEST_CASE(testConsistency) {
"\n tolerance: " << tolerance);
}

// using overnight index

auto overnight = ext::make_shared<Estr>(vars.termStructure);
Schedule overnightSchedule(bond->settlementDate(),
bond->maturityDate(),
6 * Months,
overnight->fixingCalendar(),
overnight->businessDayConvention(),
overnight->businessDayConvention(),
DateGeneration::Backward,
false);

parAssetSwap = AssetSwap(payFixedRate,
bond, bondPrice,
overnight, vars.spread,
overnightSchedule,
overnight->dayCounter(),
isPar);

swapEngine = ext::make_shared<DiscountingSwapEngine>(
vars.termStructure,
true,
bond->settlementDate(),
Settings::instance().evaluationDate());

parAssetSwap.setPricingEngine(swapEngine);
fairCleanPrice = parAssetSwap.fairCleanPrice();
fairSpread = parAssetSwap.fairSpread();

assetSwap2 = AssetSwap(payFixedRate,
bond, fairCleanPrice,
overnight, vars.spread,
overnightSchedule,
overnight->dayCounter(),
isPar);
assetSwap2.setPricingEngine(swapEngine);
if (std::fabs(assetSwap2.NPV())>tolerance) {
BOOST_FAIL("\npar asset swap fair clean price doesn't zero the NPV: " <<
std::fixed << std::setprecision(4) <<
"\n clean price: " << bondPrice <<
"\n fair clean price: " << fairCleanPrice <<
"\n NPV: " << assetSwap2.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap2.fairCleanPrice() - fairCleanPrice)>tolerance) {
BOOST_FAIL("\npar asset swap fair clean price doesn't equal input "
"clean price at zero NPV: " <<
std::fixed << std::setprecision(4) <<
"\n input clean price: " << fairCleanPrice <<
"\n fair clean price: " << assetSwap2.fairCleanPrice() <<
"\n NPV: " << assetSwap2.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap2.fairSpread() - vars.spread)>tolerance) {
BOOST_FAIL("\npar asset swap fair spread doesn't equal input spread "
"at zero NPV: " << std::fixed << std::setprecision(4) <<
"\n input spread: " << vars.spread <<
"\n fair spread: " << assetSwap2.fairSpread() <<
"\n NPV: " << assetSwap2.NPV() <<
"\n tolerance: " << tolerance);
}

assetSwap3 = AssetSwap(payFixedRate,
bond, bondPrice,
overnight, fairSpread,
overnightSchedule,
overnight->dayCounter(),
isPar);
assetSwap3.setPricingEngine(swapEngine);
if (std::fabs(assetSwap3.NPV())>tolerance) {
BOOST_FAIL("\npar asset swap fair spread doesn't zero the NPV: " <<
std::fixed << std::setprecision(4) <<
"\n spread: " << vars.spread <<
"\n fair spread: " << fairSpread <<
"\n NPV: " << assetSwap3.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap3.fairCleanPrice() - bondPrice)>tolerance) {
BOOST_FAIL("\npar asset swap fair clean price doesn't equal input "
"clean price at zero NPV: " <<
std::fixed << std::setprecision(4) <<
"\n input clean price: " << bondPrice <<
"\n fair clean price: " << assetSwap3.fairCleanPrice() <<
"\n NPV: " << assetSwap3.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap3.fairSpread() - fairSpread)>tolerance) {
BOOST_FAIL("\npar asset swap fair spread doesn't equal input spread at"
" zero NPV: " << std::fixed << std::setprecision(4) <<
"\n input spread: " << fairSpread <<
"\n fair spread: " << assetSwap3.fairSpread() <<
"\n NPV: " << assetSwap3.NPV() <<
"\n tolerance: " << tolerance);
}


// now market asset swap

isPar = false;
AssetSwap mktAssetSwap(payFixedRate,
bond, bondPrice,
Expand Down Expand Up @@ -493,6 +587,90 @@ BOOST_AUTO_TEST_CASE(testConsistency) {
"\n tolerance: " << tolerance);
}

// using overnight index

mktAssetSwap = AssetSwap(payFixedRate,
bond, bondPrice,
overnight, vars.spread,
overnightSchedule,
overnight->dayCounter(),
isPar);

swapEngine = ext::make_shared<DiscountingSwapEngine>(
vars.termStructure,
true,
bond->settlementDate(),
Settings::instance().evaluationDate());

mktAssetSwap.setPricingEngine(swapEngine);
fairCleanPrice = mktAssetSwap.fairCleanPrice();
fairSpread = mktAssetSwap.fairSpread();

assetSwap4 = AssetSwap(payFixedRate,
bond, fairCleanPrice,
overnight, vars.spread,
overnightSchedule,
overnight->dayCounter(),
isPar);
assetSwap4.setPricingEngine(swapEngine);
if (std::fabs(assetSwap4.NPV())>tolerance) {
BOOST_FAIL("\nmarket asset swap fair clean price doesn't zero the NPV: " <<
std::fixed << std::setprecision(4) <<
"\n clean price: " << bondPrice <<
"\n fair clean price: " << fairCleanPrice <<
"\n NPV: " << assetSwap4.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap4.fairCleanPrice() - fairCleanPrice)>tolerance) {
BOOST_FAIL("\nmarket asset swap fair clean price doesn't equal input "
"clean price at zero NPV: " <<
std::fixed << std::setprecision(4) <<
"\n input clean price: " << fairCleanPrice <<
"\n fair clean price: " << assetSwap4.fairCleanPrice() <<
"\n NPV: " << assetSwap4.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap4.fairSpread() - vars.spread)>tolerance) {
BOOST_FAIL("\nmarket asset swap fair spread doesn't equal input spread"
" at zero NPV: " << std::fixed << std::setprecision(4) <<
"\n input spread: " << vars.spread <<
"\n fair spread: " << assetSwap4.fairSpread() <<
"\n NPV: " << assetSwap4.NPV() <<
"\n tolerance: " << tolerance);
}

assetSwap5 = AssetSwap(payFixedRate,
bond, bondPrice,
overnight, fairSpread,
overnightSchedule,
overnight->dayCounter(),
isPar);
assetSwap5.setPricingEngine(swapEngine);
if (std::fabs(assetSwap5.NPV())>tolerance) {
BOOST_FAIL("\nmarket asset swap fair spread doesn't zero the NPV: " <<
std::fixed << std::setprecision(4) <<
"\n spread: " << vars.spread <<
"\n fair spread: " << fairSpread <<
"\n NPV: " << assetSwap5.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap5.fairCleanPrice() - bondPrice)>tolerance) {
BOOST_FAIL("\nmarket asset swap fair clean price doesn't equal input "
"clean price at zero NPV: " <<
std::fixed << std::setprecision(4) <<
"\n input clean price: " << bondPrice <<
"\n fair clean price: " << assetSwap5.fairCleanPrice() <<
"\n NPV: " << assetSwap5.NPV() <<
"\n tolerance: " << tolerance);
}
if (std::fabs(assetSwap5.fairSpread() - fairSpread)>tolerance) {
BOOST_FAIL("\nmarket asset swap fair spread doesn't equal input spread at zero NPV: " <<
std::fixed << std::setprecision(4) <<
"\n input spread: " << fairSpread <<
"\n fair spread: " << assetSwap5.fairSpread() <<
"\n NPV: " << assetSwap5.NPV() <<
"\n tolerance: " << tolerance);
}
}

BOOST_AUTO_TEST_CASE(testImpliedValue) {
Expand Down

0 comments on commit 4182cd0

Please sign in to comment.