From c56549c813e46a6aed8a3cf9c397377d1e3c160e Mon Sep 17 00:00:00 2001 From: Luigi Ballabio Date: Tue, 19 Nov 2024 17:58:15 +0100 Subject: [PATCH] Unify asset swap constructors --- ql/instruments/assetswap.cpp | 251 ++++++++++++----------------------- ql/instruments/assetswap.hpp | 12 +- 2 files changed, 93 insertions(+), 170 deletions(-) diff --git a/ql/instruments/assetswap.cpp b/ql/instruments/assetswap.cpp index 47a05b030c4..e73c084ab4c 100644 --- a/ql/instruments/assetswap.cpp +++ b/ql/instruments/assetswap.cpp @@ -42,124 +42,9 @@ namespace QuantLib { const DayCounter& floatingDayCounter, Date dealMaturity, bool payBondCoupon) - : 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(), - "deal maturity " << dealMaturity << - " cannot be later than (adjusted) bond maturity " << - tempSch.dates().back()); - QL_REQUIRE(dealMaturity > tempSch.dates()[0], - "deal maturity " << dealMaturity << - " must be later than swap start date " << - tempSch.dates()[0]); - - // the following might become an input parameter - BusinessDayConvention paymentAdjustment = Following; - - Date finalDate = tempSch.calendar().adjust( - dealMaturity, paymentAdjustment); - Schedule schedule = tempSch.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 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(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(); icashflows(); - // skip bond redemption - for (i = bondLeg.begin(); idate()<=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)) - 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 c = ext::dynamic_pointer_cast(*i); - if (c != nullptr) { - ext::shared_ptr accruedCoupon(new - SimpleCashFlow(c->accruedAmount(dealMaturity), finalDate)); - legs_[0].push_back(accruedCoupon); - } - } - // add the nonParRepayment_ - ext::shared_ptr 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 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 backPaymentCashFlow(new - SimpleCashFlow(backPayment, finalDate)); - legs_[1].push_back(backPaymentCashFlow); - } else { - // final notional exchange - ext::shared_ptr finalCashFlow (new - SimpleCashFlow(notional, finalDate)); - legs_[1].push_back(finalCashFlow); - } - - QL_REQUIRE(!legs_[0].empty(), "empty bond leg"); - for (i=legs_[0].begin(); i bond, @@ -168,9 +53,13 @@ namespace QuantLib { Spread spread, Schedule floatSchedule, const DayCounter& floatingDayCounter, - bool parSwap) - : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice), nonParRepayment_(100), - spread_(spread), parSwap_(parSwap) { + bool parSwap, + Real gearing, + Real nonParRepayment, + Date dealMaturity) + : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice), + nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) { + Schedule schedule = floatSchedule.empty() ? Schedule(bond_->settlementDate(), bond_->maturityDate(), @@ -182,25 +71,28 @@ namespace QuantLib { 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 " << + schedule.back()); + QL_REQUIRE(dealMaturity > schedule.front(), + "deal maturity " << dealMaturity << + " must be later than swap start date " << + schedule.front()); + // 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 << ")"); + dealMaturity, paymentAdjustment); + 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 @@ -209,56 +101,79 @@ namespace QuantLib { if (!parSwap_) notional *= dirtyPrice/100.0; - if (floatingDayCounter==DayCounter()) - legs_[1] = IborLeg(std::move(schedule), iborIndex) - .withNotionals(notional) - .withPaymentAdjustment(paymentAdjustment) - .withSpreads(spread); - else - legs_[1] = IborLeg(std::move(schedule), iborIndex) - .withNotionals(notional) - .withPaymentDayCounter(floatingDayCounter) - .withPaymentAdjustment(paymentAdjustment) - .withSpreads(spread); - - for (auto i=legs_[1].begin(); icashflows(); - 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)) + 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); } - QL_REQUIRE(!legs_[0].empty(), - "empty bond leg to start with"); + // 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(*i); + if (c != nullptr) { + Real accruedAmount = c->accruedAmount(dealMaturity); + auto accruedCoupon = + ext::make_shared(accruedAmount, finalDate); + legs_[0].push_back(accruedCoupon); + } + } + + // add the redemption, or whatever the final payment is + if (nonParRepayment_ == Null()) { + auto redemption = bondLeg.back(); + auto finalFlow = + ext::make_shared(redemption->amount(), finalDate); + legs_[0].push_back(finalFlow); + nonParRepayment_ = 100.0; + } else { + auto finalFlow = + ext::make_shared(nonParRepayment_, finalDate); + legs_[0].push_back(finalFlow); + } + + /******** Floating leg ********/ + + legs_[1] = + IborLeg(std::move(schedule), iborIndex) + .withNotionals(notional) + .withPaymentAdjustment(paymentAdjustment) + .withGearings(gearing) + .withSpreads(spread) + .withPaymentDayCounter(floatingDayCounter); - // special flows if (parSwap_) { - // upfront on the floating leg - Real upfront = (dirtyPrice-100.0)/100.0*notional; - ext::shared_ptr upfrontCashFlow(new - SimpleCashFlow(upfront, upfrontDate_)); + // upfront + Real upfront = (dirtyPrice-100.0)/100.0 * notional; + auto upfrontCashFlow = + ext::make_shared(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 backPaymentCashFlow(new - SimpleCashFlow(backPayment, finalDate)); + auto backPaymentCashFlow = + ext::make_shared(backPayment, finalDate); legs_[1].push_back(backPaymentCashFlow); } else { // final notional exchange - ext::shared_ptr finalCashFlow(new - SimpleCashFlow(notional, finalDate)); + auto finalCashFlow = + ext::make_shared(notional, finalDate); legs_[1].push_back(finalCashFlow); } - QL_REQUIRE(!legs_[0].empty(), "empty bond leg"); - for (auto i=legs_[0].begin(); i(), + 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, Real bondCleanPrice, @@ -72,6 +79,7 @@ namespace QuantLib { const DayCounter& floatingDayCount = DayCounter(), Date dealMaturity = Date(), bool payBondCoupon = false); + // results Spread fairSpread() const; Real floatingLegBPS() const;