• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lballabio / QuantLib / 27969163720

22 Jun 2026 04:48PM UTC coverage: 74.844% (+0.001%) from 74.843%
27969163720

Pull #2638

github

web-flow
Merge 42486f796 into 37ef07e19
Pull Request #2638: introduce CashFlow::isCoupon() to replace dynamic casts

33 of 41 new or added lines in 15 files covered. (80.49%)

59254 of 79170 relevant lines covered (74.84%)

8633976.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

57.59
/ql/instruments/assetswap.cpp
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 payBondCoupon,
96✔
37
                         ext::shared_ptr<Bond> bond,
38
                         Real bondCleanPrice,
39
                         const ext::shared_ptr<IborIndex>& iborIndex,
40
                         Spread spread,
41
                         Schedule floatSchedule,
42
                         const DayCounter& floatingDayCounter,
43
                         bool parSwap,
44
                         Real gearing,
45
                         Real nonParRepayment,
46
                         Date dealMaturity)
96✔
47
    : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice),
96✔
48
      nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) {
96✔
49

50
        auto overnight = ext::dynamic_pointer_cast<OvernightIndex>(iborIndex);
96✔
51
        if (overnight) {
96✔
52
            QL_REQUIRE(!floatSchedule.empty(),
6✔
53
                       "floating schedule is needed when using an overnight index");
54
        }
55

56
        Schedule schedule = floatSchedule.empty()
57
            ? Schedule(bond_->settlementDate(),
58
                       bond_->maturityDate(),
90✔
59
                       iborIndex->tenor(),
90✔
60
                       iborIndex->fixingCalendar(),
90✔
61
                       iborIndex->businessDayConvention(),
62
                       iborIndex->businessDayConvention(),
63
                       DateGeneration::Backward,
64
                       false) // endOfMonth
65
            : std::move(floatSchedule);
186✔
66

67
        if (dealMaturity == Date())
96✔
68
            dealMaturity = schedule.back();
96✔
69
        QL_REQUIRE(dealMaturity <= schedule.back(),
96✔
70
                   "deal maturity " << dealMaturity <<
71
                   " cannot be later than (adjusted) bond maturity " <<
72
                   schedule.back());
73
        QL_REQUIRE(dealMaturity > schedule.front(),
96✔
74
                   "deal maturity " << dealMaturity <<
75
                   " must be later than swap start date " <<
76
                   schedule.front());
77

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

81
        Date finalDate = schedule.calendar().adjust(
96✔
82
            dealMaturity, paymentAdjustment);
83
        schedule = schedule.until(finalDate);
96✔
84

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

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

97
        /******** Bond leg ********/
98

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

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

105
        // add coupons for the time being, not the redemption
106
        Leg::const_iterator i;
107
        for (i = bondLeg.begin(); i < bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
1,798✔
108
            if (!(*i)->hasOccurred(upfrontDate_, includeOnUpfrontDate))
1,702✔
109
                legs_[0].push_back(*i);
1,490✔
110
        }
111

112
        // if we're skipping a cashflow before the redemption
113
        // and it's a coupon, then add the accrued coupon.
114
        if (i < bondLeg.end()-1) {
96✔
NEW
115
            auto c = coupon_cast(*i);
×
116
            if (c != nullptr) {
×
117
                Real accruedAmount = c->accruedAmount(dealMaturity);
×
118
                auto accruedCoupon =
119
                    ext::make_shared<SimpleCashFlow>(accruedAmount, finalDate);
×
120
                legs_[0].push_back(accruedCoupon);
×
121
            }
122
        }
123

124
        // add the redemption, or whatever the final payment is
125
        if (nonParRepayment_ == Null<Real>()) {
96✔
126
            auto redemption = bondLeg.back();
127
            auto finalFlow =
128
                ext::make_shared<SimpleCashFlow>(redemption->amount(), finalDate);
96✔
129
            legs_[0].push_back(finalFlow);
×
130
            nonParRepayment_ = 100.0;
96✔
131
        } else {
132
            auto finalFlow =
133
                ext::make_shared<SimpleCashFlow>(nonParRepayment_, finalDate);
×
134
            legs_[0].push_back(finalFlow);
×
135
        }
136

137
        /******** Floating leg ********/
138

139
        if (overnight) {
96✔
140
            legs_[1] =
141
                OvernightLeg(schedule, overnight)
12✔
142
                .withNotionals(notional)
6✔
143
                .withPaymentAdjustment(paymentAdjustment)
6✔
144
                .withGearings(gearing)
6✔
145
                .withSpreads(spread)
6✔
146
                .withPaymentDayCounter(floatingDayCounter);
12✔
147
        } else {
148
            legs_[1] =
149
                IborLeg(std::move(schedule), iborIndex)
180✔
150
                .withNotionals(notional)
90✔
151
                .withPaymentAdjustment(paymentAdjustment)
90✔
152
                .withGearings(gearing)
90✔
153
                .withSpreads(spread)
90✔
154
                .withPaymentDayCounter(floatingDayCounter);
180✔
155
        }
156

157
        if (parSwap_) {
96✔
158
            // upfront
159
            Real upfront = (dirtyPrice-100.0)/100.0 * notional;
72✔
160
            auto upfrontCashFlow =
161
                ext::make_shared<SimpleCashFlow>(upfront, upfrontDate_);
72✔
162
            legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
72✔
163
            // backpayment (accounts for non-par redemption, if any)
164
            Real backPayment = notional;
72✔
165
            auto backPaymentCashFlow =
166
                ext::make_shared<SimpleCashFlow>(backPayment, finalDate);
72✔
167
            legs_[1].push_back(backPaymentCashFlow);
72✔
168
        } else {
169
            // final notional exchange
170
            auto finalCashFlow =
171
                ext::make_shared<SimpleCashFlow>(notional, finalDate);
24✔
172
            legs_[1].push_back(finalCashFlow);
24✔
173
        }
174

175
        /******** registration and sides ********/
176

177
        for (const auto& leg: legs_)
288✔
178
            for (const auto& c: leg)
5,146✔
179
                registerWith(c);
9,908✔
180

181
        if (payBondCoupon) {
96✔
182
            payer_[0]=-1.0;
96✔
183
            payer_[1]=+1.0;
96✔
184
        } else {
185
            payer_[0]=+1.0;
×
186
            payer_[1]=-1.0;
×
187
        }
188
    }
192✔
189

190
    void AssetSwap::setupArguments(PricingEngine::arguments* args) const {
98✔
191

192
        Swap::setupArguments(args);
98✔
193

194
        auto* arguments = dynamic_cast<AssetSwap::arguments*>(args);
98✔
195

196
        if (arguments == nullptr) // it's a swap engine...
98✔
197
            return;
198

199
        const Leg& fixedCoupons = bondLeg();
200

201
        arguments->fixedResetDates = arguments->fixedPayDates =
202
            vector<Date>(fixedCoupons.size());
×
203
        arguments->fixedCoupons = vector<Real>(fixedCoupons.size());
×
204

205
        for (Size i=0; i<fixedCoupons.size(); ++i) {
×
206
            ext::shared_ptr<FixedRateCoupon> coupon =
207
                ext::dynamic_pointer_cast<FixedRateCoupon>(fixedCoupons[i]);
×
208

209
            arguments->fixedPayDates[i] = coupon->date();
×
210
            arguments->fixedResetDates[i] = coupon->accrualStartDate();
×
211
            arguments->fixedCoupons[i] = coupon->amount();
×
212
        }
213

214
        const Leg& floatingCoupons = floatingLeg();
215

216
        arguments->floatingResetDates = arguments->floatingPayDates =
217
            arguments->floatingFixingDates =
218
            vector<Date>(floatingCoupons.size());
×
219
        arguments->floatingAccrualTimes =
220
            vector<Time>(floatingCoupons.size());
×
221
        arguments->floatingSpreads =
222
            vector<Spread>(floatingCoupons.size());
×
223

224
        for (Size i=0; i<floatingCoupons.size(); ++i) {
×
225
            ext::shared_ptr<FloatingRateCoupon> coupon =
226
                ext::dynamic_pointer_cast<FloatingRateCoupon>(floatingCoupons[i]);
×
227

228
            arguments->floatingResetDates[i] = coupon->accrualStartDate();
×
229
            arguments->floatingPayDates[i] = coupon->date();
×
230
            arguments->floatingFixingDates[i] = coupon->fixingDate();
×
231
            arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
×
232
            arguments->floatingSpreads[i] = coupon->spread();
×
233
        }
234
    }
235

236
    Spread AssetSwap::fairSpread() const {
66✔
237
        static const Spread basisPoint = 1.0e-4;
238
        calculate();
66✔
239
        if (fairSpread_ != Null<Spread>()) {
66✔
240
            return fairSpread_;
241
        } else if (legBPS_.size() > 1 && legBPS_[1] != Null<Spread>()) {
66✔
242
            fairSpread_ = spread_ - NPV_/legBPS_[1]*basisPoint;
66✔
243
            return fairSpread_;
66✔
244
        } else {
245
            QL_FAIL("fair spread not available");
×
246
        }
247
    }
248

249
    Real AssetSwap::floatingLegBPS() const {
×
250
        calculate();
×
251
        QL_REQUIRE(legBPS_.size() > 1 && legBPS_[1] != Null<Real>(),
×
252
                   "floating-leg BPS not available");
253
        return legBPS_[1];
×
254
    }
255

256
    Real AssetSwap::floatingLegNPV() const {
×
257
        calculate();
×
258
        QL_REQUIRE(legNPV_.size() > 1 && legNPV_[1] != Null<Real>(),
×
259
                   "floating-leg NPV not available");
260
        return legNPV_[1];
×
261
    }
262

263
    Real AssetSwap::fairCleanPrice() const {
50✔
264
        calculate();
50✔
265
        if (fairCleanPrice_ != Null<Real>()) {
50✔
266
            return fairCleanPrice_;
267
        } else {
268
            QL_REQUIRE(startDiscounts_[1]!=Null<DiscountFactor>(),
50✔
269
                       "fair clean price not available for seasoned deal");
270
            Real notional = bond_->notional(upfrontDate_);
50✔
271
            if (parSwap_) {
50✔
272
                fairCleanPrice_ = bondCleanPrice_ - payer_[1] *
41✔
273
                    NPV_*npvDateDiscount_/startDiscounts_[1]/(notional/100.0);
41✔
274
            } else {
275
                Real accruedAmount = bond_->accruedAmount(upfrontDate_);
9✔
276
                Real dirtyPrice = bondCleanPrice_ + accruedAmount;
9✔
277
                Real fairDirtyPrice = - legNPV_[0]/legNPV_[1] * dirtyPrice;
9✔
278
                fairCleanPrice_ = fairDirtyPrice - accruedAmount;
9✔
279
            }
280

281
            return fairCleanPrice_;
50✔
282
        }
283
    }
284

285
    Real AssetSwap::fairNonParRepayment() const {
×
286
        calculate();
×
287
        if (fairNonParRepayment_ != Null<Real>()) {
×
288
            return fairNonParRepayment_;
289
        } else {
290
            QL_REQUIRE(endDiscounts_[1]!=Null<DiscountFactor>(),
×
291
                       "fair non par repayment not available for expired leg");
292
            Real notional = bond_->notional(upfrontDate_);
×
293
            fairNonParRepayment_ = nonParRepayment_ - payer_[0] *
×
294
                NPV_*npvDateDiscount_/endDiscounts_[1]/(notional/100.0);
×
295
            return fairNonParRepayment_;
×
296
        }
297
    }
298

299
    void AssetSwap::setupExpired() const {
×
300
        Swap::setupExpired();
×
301
        fairSpread_ = Null<Spread>();
×
302
        fairCleanPrice_ = Null<Real>();
×
303
        fairNonParRepayment_ = Null<Real>();
×
304
    }
×
305

306
    void AssetSwap::fetchResults(const PricingEngine::results* r) const {
98✔
307
        Swap::fetchResults(r);
98✔
308
        const auto* results = dynamic_cast<const AssetSwap::results*>(r);
98✔
309
        if (results != nullptr) {
98✔
310
            fairSpread_ = results->fairSpread;
×
311
            fairCleanPrice_= results->fairCleanPrice;
×
312
            fairNonParRepayment_= results->fairNonParRepayment;
×
313
        } else {
314
            fairSpread_ = Null<Spread>();
98✔
315
            fairCleanPrice_ = Null<Real>();
98✔
316
            fairNonParRepayment_ = Null<Real>();
98✔
317
        }
318
    }
98✔
319

320
    void AssetSwap::arguments::validate() const {
×
321
        QL_REQUIRE(fixedResetDates.size() == fixedPayDates.size(),
×
322
                   "number of fixed start dates different from "
323
                   "number of fixed payment dates");
324
        QL_REQUIRE(fixedPayDates.size() == fixedCoupons.size(),
×
325
                   "number of fixed payment dates different from "
326
                   "number of fixed coupon amounts");
327
        QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
×
328
                   "number of floating start dates different from "
329
                   "number of floating payment dates");
330
        QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
×
331
                   "number of floating fixing dates different from "
332
                   "number of floating payment dates");
333
        QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
×
334
                   "number of floating accrual times different from "
335
                   "number of floating payment dates");
336
        QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
×
337
                   "number of floating spreads different from "
338
                   "number of floating payment dates");
339
    }
×
340

341
    void AssetSwap::results::reset() {
×
342
        Swap::results::reset();
×
343
        fairSpread = Null<Spread>();
×
344
        fairCleanPrice = Null<Real>();
×
345
        fairNonParRepayment = Null<Real>();
×
346
    }
×
347

348
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc