Coverage Report

Created: 2025-12-08 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/quantlib/ql/instruments/assetswap.cpp
Line
Count
Source
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2006, 2007 Chiara Fornarola
5
 Copyright (C) 2007, 2009, 2011 Ferdinando Ametrano
6
 Copyright (C) 2007, 2009 StatPro Italia srl
7
8
 This file is part of QuantLib, a free-software/open-source library
9
 for financial quantitative analysts and developers - http://quantlib.org/
10
11
 QuantLib is free software: you can redistribute it and/or modify it
12
 under the terms of the QuantLib license.  You should have received a
13
 copy of the license along with this program; if not, please email
14
 <quantlib-dev@lists.sf.net>. The license is also available online at
15
 <https://www.quantlib.org/license.shtml>.
16
17
 This program is distributed in the hope that it will be useful, but WITHOUT
18
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19
 FOR A PARTICULAR PURPOSE.  See the license for more details.
20
*/
21
22
#include <ql/cashflows/cashflowvectors.hpp>
23
#include <ql/cashflows/couponpricer.hpp>
24
#include <ql/cashflows/fixedratecoupon.hpp>
25
#include <ql/cashflows/iborcoupon.hpp>
26
#include <ql/cashflows/overnightindexedcoupon.hpp>
27
#include <ql/cashflows/simplecashflow.hpp>
28
#include <ql/instruments/assetswap.hpp>
29
#include <ql/pricingengines/swap/discountingswapengine.hpp>
30
#include <utility>
31
32
using std::vector;
33
34
namespace QuantLib {
35
36
    AssetSwap::AssetSwap(bool parSwap,
37
                         ext::shared_ptr<Bond> bond,
38
                         Real bondCleanPrice,
39
                         Real nonParRepayment,
40
                         Real gearing,
41
                         const ext::shared_ptr<IborIndex>& iborIndex,
42
                         Spread spread,
43
                         const DayCounter& floatingDayCounter,
44
                         Date dealMaturity,
45
                         bool payBondCoupon)
46
0
    : AssetSwap(payBondCoupon, std::move(bond), bondCleanPrice, iborIndex, spread,
47
0
                Schedule(), floatingDayCounter, parSwap, gearing,
48
0
                nonParRepayment, dealMaturity) {}
Unexecuted instantiation: QuantLib::AssetSwap::AssetSwap(bool, boost::shared_ptr<QuantLib::Bond>, double, double, double, boost::shared_ptr<QuantLib::IborIndex> const&, double, QuantLib::DayCounter const&, QuantLib::Date, bool)
Unexecuted instantiation: QuantLib::AssetSwap::AssetSwap(bool, boost::shared_ptr<QuantLib::Bond>, double, double, double, boost::shared_ptr<QuantLib::IborIndex> const&, double, QuantLib::DayCounter const&, QuantLib::Date, bool)
49
50
    AssetSwap::AssetSwap(bool payBondCoupon,
51
                         ext::shared_ptr<Bond> bond,
52
                         Real bondCleanPrice,
53
                         const ext::shared_ptr<IborIndex>& iborIndex,
54
                         Spread spread,
55
                         Schedule floatSchedule,
56
                         const DayCounter& floatingDayCounter,
57
                         bool parSwap,
58
                         Real gearing,
59
                         Real nonParRepayment,
60
                         Date dealMaturity)
61
0
    : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice),
62
0
      nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) {
63
64
0
        auto overnight = ext::dynamic_pointer_cast<OvernightIndex>(iborIndex);
65
0
        if (overnight) {
66
0
            QL_REQUIRE(!floatSchedule.empty(),
67
0
                       "floating schedule is needed when using an overnight index");
68
0
        }
69
70
0
        Schedule schedule = floatSchedule.empty()
71
0
            ? Schedule(bond_->settlementDate(),
72
0
                       bond_->maturityDate(),
73
0
                       iborIndex->tenor(),
74
0
                       iborIndex->fixingCalendar(),
75
0
                       iborIndex->businessDayConvention(),
76
0
                       iborIndex->businessDayConvention(),
77
0
                       DateGeneration::Backward,
78
0
                       false) // endOfMonth
79
0
            : std::move(floatSchedule);
80
81
0
        if (dealMaturity == Date())
82
0
            dealMaturity = schedule.back();
83
0
        QL_REQUIRE(dealMaturity <= schedule.back(),
84
0
                   "deal maturity " << dealMaturity <<
85
0
                   " cannot be later than (adjusted) bond maturity " <<
86
0
                   schedule.back());
87
0
        QL_REQUIRE(dealMaturity > schedule.front(),
88
0
                   "deal maturity " << dealMaturity <<
89
0
                   " must be later than swap start date " <<
90
0
                   schedule.front());
91
92
        // the following might become an input parameter
93
0
        BusinessDayConvention paymentAdjustment = Following;
94
95
0
        Date finalDate = schedule.calendar().adjust(
96
0
            dealMaturity, paymentAdjustment);
97
0
        schedule = schedule.until(finalDate);
98
99
        // bondCleanPrice must be the (forward) clean price
100
        // at the floating schedule start date
101
0
        upfrontDate_ = schedule.startDate();
102
0
        Real dirtyPrice = bondCleanPrice_ + bond_->accruedAmount(upfrontDate_);
103
104
0
        Real notional = bond_->notional(upfrontDate_);
105
        /* In the market asset swap, the bond is purchased in return for
106
           payment of the full price. The notional of the floating leg is
107
           then scaled by the full price. */
108
0
        if (!parSwap_)
109
0
            notional *= dirtyPrice/100.0;
110
111
        /******** Bond leg ********/
112
113
0
        const Leg& bondLeg = bond_->cashflows();
114
0
        QL_REQUIRE(!bondLeg.empty(), "no cashflows from bond");
115
116
0
        bool includeOnUpfrontDate = false; // a cash flow on the upfront
117
                                           // date must be discarded
118
119
        // add coupons for the time being, not the redemption
120
0
        Leg::const_iterator i;
121
0
        for (i = bondLeg.begin(); i < bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
122
0
            if (!(*i)->hasOccurred(upfrontDate_, includeOnUpfrontDate))
123
0
                legs_[0].push_back(*i);
124
0
        }
125
126
        // if we're skipping a cashflow before the redemption
127
        // and it's a coupon, then add the accrued coupon.
128
0
        if (i < bondLeg.end()-1) {
129
0
            auto c = ext::dynamic_pointer_cast<Coupon>(*i);
130
0
            if (c != nullptr) {
131
0
                Real accruedAmount = c->accruedAmount(dealMaturity);
132
0
                auto accruedCoupon =
133
0
                    ext::make_shared<SimpleCashFlow>(accruedAmount, finalDate);
134
0
                legs_[0].push_back(accruedCoupon);
135
0
            }
136
0
        }
137
138
        // add the redemption, or whatever the final payment is
139
0
        if (nonParRepayment_ == Null<Real>()) {
140
0
            auto redemption = bondLeg.back();
141
0
            auto finalFlow =
142
0
                ext::make_shared<SimpleCashFlow>(redemption->amount(), finalDate);
143
0
            legs_[0].push_back(finalFlow);
144
0
            nonParRepayment_ = 100.0;
145
0
        } else {
146
0
            auto finalFlow =
147
0
                ext::make_shared<SimpleCashFlow>(nonParRepayment_, finalDate);
148
0
            legs_[0].push_back(finalFlow);
149
0
        }
150
151
        /******** Floating leg ********/
152
153
0
        if (overnight) {
154
0
            legs_[1] =
155
0
                OvernightLeg(std::move(schedule), overnight)
156
0
                .withNotionals(notional)
157
0
                .withPaymentAdjustment(paymentAdjustment)
158
0
                .withGearings(gearing)
159
0
                .withSpreads(spread)
160
0
                .withPaymentDayCounter(floatingDayCounter);
161
0
        } else {
162
0
            legs_[1] =
163
0
                IborLeg(std::move(schedule), iborIndex)
164
0
                .withNotionals(notional)
165
0
                .withPaymentAdjustment(paymentAdjustment)
166
0
                .withGearings(gearing)
167
0
                .withSpreads(spread)
168
0
                .withPaymentDayCounter(floatingDayCounter);
169
0
        }
170
171
0
        if (parSwap_) {
172
            // upfront
173
0
            Real upfront = (dirtyPrice-100.0)/100.0 * notional;
174
0
            auto upfrontCashFlow =
175
0
                ext::make_shared<SimpleCashFlow>(upfront, upfrontDate_);
176
0
            legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
177
            // backpayment (accounts for non-par redemption, if any)
178
0
            Real backPayment = notional;
179
0
            auto backPaymentCashFlow =
180
0
                ext::make_shared<SimpleCashFlow>(backPayment, finalDate);
181
0
            legs_[1].push_back(backPaymentCashFlow);
182
0
        } else {
183
            // final notional exchange
184
0
            auto finalCashFlow =
185
0
                ext::make_shared<SimpleCashFlow>(notional, finalDate);
186
0
            legs_[1].push_back(finalCashFlow);
187
0
        }
188
189
        /******** registration and sides ********/
190
191
0
        for (const auto& leg: legs_)
192
0
            for (const auto& c: leg)
193
0
                registerWith(c);
194
195
0
        if (payBondCoupon) {
196
0
            payer_[0]=-1.0;
197
0
            payer_[1]=+1.0;
198
0
        } else {
199
0
            payer_[0]=+1.0;
200
0
            payer_[1]=-1.0;
201
0
        }
202
0
    }
Unexecuted instantiation: QuantLib::AssetSwap::AssetSwap(bool, boost::shared_ptr<QuantLib::Bond>, double, boost::shared_ptr<QuantLib::IborIndex> const&, double, QuantLib::Schedule, QuantLib::DayCounter const&, bool, double, double, QuantLib::Date)
Unexecuted instantiation: QuantLib::AssetSwap::AssetSwap(bool, boost::shared_ptr<QuantLib::Bond>, double, boost::shared_ptr<QuantLib::IborIndex> const&, double, QuantLib::Schedule, QuantLib::DayCounter const&, bool, double, double, QuantLib::Date)
203
204
0
    void AssetSwap::setupArguments(PricingEngine::arguments* args) const {
205
206
0
        Swap::setupArguments(args);
207
208
0
        auto* arguments = dynamic_cast<AssetSwap::arguments*>(args);
209
210
0
        if (arguments == nullptr) // it's a swap engine...
211
0
            return;
212
213
0
        const Leg& fixedCoupons = bondLeg();
214
215
0
        arguments->fixedResetDates = arguments->fixedPayDates =
216
0
            vector<Date>(fixedCoupons.size());
217
0
        arguments->fixedCoupons = vector<Real>(fixedCoupons.size());
218
219
0
        for (Size i=0; i<fixedCoupons.size(); ++i) {
220
0
            ext::shared_ptr<FixedRateCoupon> coupon =
221
0
                ext::dynamic_pointer_cast<FixedRateCoupon>(fixedCoupons[i]);
222
223
0
            arguments->fixedPayDates[i] = coupon->date();
224
0
            arguments->fixedResetDates[i] = coupon->accrualStartDate();
225
0
            arguments->fixedCoupons[i] = coupon->amount();
226
0
        }
227
228
0
        const Leg& floatingCoupons = floatingLeg();
229
230
0
        arguments->floatingResetDates = arguments->floatingPayDates =
231
0
            arguments->floatingFixingDates =
232
0
            vector<Date>(floatingCoupons.size());
233
0
        arguments->floatingAccrualTimes =
234
0
            vector<Time>(floatingCoupons.size());
235
0
        arguments->floatingSpreads =
236
0
            vector<Spread>(floatingCoupons.size());
237
238
0
        for (Size i=0; i<floatingCoupons.size(); ++i) {
239
0
            ext::shared_ptr<FloatingRateCoupon> coupon =
240
0
                ext::dynamic_pointer_cast<FloatingRateCoupon>(floatingCoupons[i]);
241
242
0
            arguments->floatingResetDates[i] = coupon->accrualStartDate();
243
0
            arguments->floatingPayDates[i] = coupon->date();
244
0
            arguments->floatingFixingDates[i] = coupon->fixingDate();
245
0
            arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
246
0
            arguments->floatingSpreads[i] = coupon->spread();
247
0
        }
248
0
    }
249
250
0
    Spread AssetSwap::fairSpread() const {
251
0
        static const Spread basisPoint = 1.0e-4;
252
0
        calculate();
253
0
        if (fairSpread_ != Null<Spread>()) {
254
0
            return fairSpread_;
255
0
        } else if (legBPS_.size() > 1 && legBPS_[1] != Null<Spread>()) {
256
0
            fairSpread_ = spread_ - NPV_/legBPS_[1]*basisPoint;
257
0
            return fairSpread_;
258
0
        } else {
259
0
            QL_FAIL("fair spread not available");
260
0
        }
261
0
    }
262
263
0
    Real AssetSwap::floatingLegBPS() const {
264
0
        calculate();
265
0
        QL_REQUIRE(legBPS_.size() > 1 && legBPS_[1] != Null<Real>(),
266
0
                   "floating-leg BPS not available");
267
0
        return legBPS_[1];
268
0
    }
269
270
0
    Real AssetSwap::floatingLegNPV() const {
271
0
        calculate();
272
0
        QL_REQUIRE(legNPV_.size() > 1 && legNPV_[1] != Null<Real>(),
273
0
                   "floating-leg NPV not available");
274
0
        return legNPV_[1];
275
0
    }
276
277
0
    Real AssetSwap::fairCleanPrice() const {
278
0
        calculate();
279
0
        if (fairCleanPrice_ != Null<Real>()) {
280
0
            return fairCleanPrice_;
281
0
        } else {
282
0
            QL_REQUIRE(startDiscounts_[1]!=Null<DiscountFactor>(),
283
0
                       "fair clean price not available for seasoned deal");
284
0
            Real notional = bond_->notional(upfrontDate_);
285
0
            if (parSwap_) {
286
0
                fairCleanPrice_ = bondCleanPrice_ - payer_[1] *
287
0
                    NPV_*npvDateDiscount_/startDiscounts_[1]/(notional/100.0);
288
0
            } else {
289
0
                Real accruedAmount = bond_->accruedAmount(upfrontDate_);
290
0
                Real dirtyPrice = bondCleanPrice_ + accruedAmount;
291
0
                Real fairDirtyPrice = - legNPV_[0]/legNPV_[1] * dirtyPrice;
292
0
                fairCleanPrice_ = fairDirtyPrice - accruedAmount;
293
0
            }
294
295
0
            return fairCleanPrice_;
296
0
        }
297
0
    }
298
299
0
    Real AssetSwap::fairNonParRepayment() const {
300
0
        calculate();
301
0
        if (fairNonParRepayment_ != Null<Real>()) {
302
0
            return fairNonParRepayment_;
303
0
        } else {
304
0
            QL_REQUIRE(endDiscounts_[1]!=Null<DiscountFactor>(),
305
0
                       "fair non par repayment not available for expired leg");
306
0
            Real notional = bond_->notional(upfrontDate_);
307
0
            fairNonParRepayment_ = nonParRepayment_ - payer_[0] *
308
0
                NPV_*npvDateDiscount_/endDiscounts_[1]/(notional/100.0);
309
0
            return fairNonParRepayment_;
310
0
        }
311
0
    }
312
313
0
    void AssetSwap::setupExpired() const {
314
0
        Swap::setupExpired();
315
0
        fairSpread_ = Null<Spread>();
316
0
        fairCleanPrice_ = Null<Real>();
317
0
        fairNonParRepayment_ = Null<Real>();
318
0
    }
319
320
0
    void AssetSwap::fetchResults(const PricingEngine::results* r) const {
321
0
        Swap::fetchResults(r);
322
0
        const auto* results = dynamic_cast<const AssetSwap::results*>(r);
323
0
        if (results != nullptr) {
324
0
            fairSpread_ = results->fairSpread;
325
0
            fairCleanPrice_= results->fairCleanPrice;
326
0
            fairNonParRepayment_= results->fairNonParRepayment;
327
0
        } else {
328
0
            fairSpread_ = Null<Spread>();
329
0
            fairCleanPrice_ = Null<Real>();
330
0
            fairNonParRepayment_ = Null<Real>();
331
0
        }
332
0
    }
333
334
0
    void AssetSwap::arguments::validate() const {
335
0
        QL_REQUIRE(fixedResetDates.size() == fixedPayDates.size(),
336
0
                   "number of fixed start dates different from "
337
0
                   "number of fixed payment dates");
338
0
        QL_REQUIRE(fixedPayDates.size() == fixedCoupons.size(),
339
0
                   "number of fixed payment dates different from "
340
0
                   "number of fixed coupon amounts");
341
0
        QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
342
0
                   "number of floating start dates different from "
343
0
                   "number of floating payment dates");
344
0
        QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
345
0
                   "number of floating fixing dates different from "
346
0
                   "number of floating payment dates");
347
0
        QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
348
0
                   "number of floating accrual times different from "
349
0
                   "number of floating payment dates");
350
0
        QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
351
0
                   "number of floating spreads different from "
352
0
                   "number of floating payment dates");
353
0
    }
354
355
0
    void AssetSwap::results::reset() {
356
0
        Swap::results::reset();
357
0
        fairSpread = Null<Spread>();
358
0
        fairCleanPrice = Null<Real>();
359
0
        fairNonParRepayment = Null<Real>();
360
0
    }
361
362
}