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

lballabio / QuantLib / 8596367794

08 Apr 2024 07:41AM UTC coverage: 72.497%. Remained the same
8596367794

push

github

web-flow
Pass Schedule by value and move (#1942)

55 of 68 new or added lines in 27 files covered. (80.88%)

1 existing line in 1 file now uncovered.

54964 of 75816 relevant lines covered (72.5%)

8676660.23 hits per line

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

37.93
/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
 <http://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/simplecashflow.hpp>
27
#include <ql/instruments/assetswap.hpp>
28
#include <ql/pricingengines/swap/discountingswapengine.hpp>
29
#include <utility>
30

31
using std::vector;
32

33
namespace QuantLib {
34

35
    AssetSwap::AssetSwap(bool parSwap,
×
36
                         ext::shared_ptr<Bond> bond,
37
                         Real bondCleanPrice,
38
                         Real nonParRepayment,
39
                         Real gearing,
40
                         const ext::shared_ptr<IborIndex>& iborIndex,
41
                         Spread spread,
42
                         const DayCounter& floatingDayCounter,
43
                         Date dealMaturity,
44
                         bool payBondCoupon)
×
45
    : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice),
×
46
      nonParRepayment_(nonParRepayment), spread_(spread), parSwap_(parSwap) {
×
47
        Schedule tempSch(bond_->settlementDate(),
48
                         bond_->maturityDate(),
×
49
                         iborIndex->tenor(),
×
50
                         iborIndex->fixingCalendar(),
×
51
                         iborIndex->businessDayConvention(),
52
                         iborIndex->businessDayConvention(),
53
                         DateGeneration::Backward,
54
                         false); // endOfMonth
×
55
        if (dealMaturity==Date())
×
56
            dealMaturity = bond_->maturityDate();
×
57
        QL_REQUIRE(dealMaturity <= tempSch.dates().back(),
×
58
                   "deal maturity " << dealMaturity <<
59
                   " cannot be later than (adjusted) bond maturity " <<
60
                   tempSch.dates().back());
61
        QL_REQUIRE(dealMaturity > tempSch.dates()[0],
×
62
                   "deal maturity " << dealMaturity <<
63
                   " must be later than swap start date " <<
64
                   tempSch.dates()[0]);
65

66
        // the following might become an input parameter
67
        BusinessDayConvention paymentAdjustment = Following;
68

69
        Date finalDate = tempSch.calendar().adjust(
×
70
            dealMaturity, paymentAdjustment);
71
        Schedule schedule = tempSch.until(finalDate);
×
72

73
        // bondCleanPrice must be the (forward) clean price
74
        // at the floating schedule start date
75
        upfrontDate_ = schedule.startDate();
×
76
        Real dirtyPrice = bondCleanPrice_ +
×
77
                          bond_->accruedAmount(upfrontDate_);
×
78

79
        Real notional = bond_->notional(upfrontDate_);
×
80
        /* In the market asset swap, the bond is purchased in return for
81
           payment of the full price. The notional of the floating leg is
82
           then scaled by the full price. */
83
        if (!parSwap_)
×
84
            notional *= dirtyPrice/100.0;
×
85

86
        if (floatingDayCounter==DayCounter())
×
87
            legs_[1] = IborLeg(schedule, iborIndex)
×
88
                .withNotionals(notional)
×
89
                .withPaymentAdjustment(paymentAdjustment)
×
90
                .withGearings(gearing)
×
91
                .withSpreads(spread);
×
92
        else
93
            legs_[1] = IborLeg(schedule, iborIndex)
×
94
                .withNotionals(notional)
×
95
                .withPaymentDayCounter(floatingDayCounter)
×
96
                .withPaymentAdjustment(paymentAdjustment)
×
97
                .withGearings(gearing)
×
98
                .withSpreads(spread);
×
99

100
        Leg::const_iterator i;
101
        for (i=legs_[1].begin(); i<legs_[1].end(); ++i)
×
102
            registerWith(*i);
×
103

104
        const Leg& bondLeg = bond_->cashflows();
×
105
        // skip bond redemption
106
        for (i = bondLeg.begin(); i<bondLeg.end()-1 && (*i)->date()<=dealMaturity; ++i) {
×
107
            // whatever might be the choice for the discounting engine
108
            // bond flows on upfrontDate_ must be discarded
109
            bool upfrontDateBondFlows = false;
110
            if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
×
111
                legs_[0].push_back(*i);
×
112
        }
113
        // if the first skipped cashflow is not the redemption
114
        // and it is a coupon then add the accrued coupon
115
        if (i<bondLeg.end()-1) {
×
116
            ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(*i);
×
117
            if (c != nullptr) {
×
118
                ext::shared_ptr<CashFlow> accruedCoupon(new
119
                    SimpleCashFlow(c->accruedAmount(dealMaturity), finalDate));
×
120
                legs_[0].push_back(accruedCoupon);
×
121
            }
122
        }
123
        // add the nonParRepayment_
124
        ext::shared_ptr<CashFlow> nonParRepaymentFlow(new
125
            SimpleCashFlow(nonParRepayment_, finalDate));
×
126
        legs_[0].push_back(nonParRepaymentFlow);
×
127

128
        QL_REQUIRE(!legs_[0].empty(),
×
129
                   "empty bond leg to start with");
130

131
        // special flows
132
        if (parSwap_) {
×
133
            // upfront on the floating leg
134
            Real upfront = (dirtyPrice-100.0)/100.0*notional;
×
135
            ext::shared_ptr<CashFlow> upfrontCashFlow(new
136
                SimpleCashFlow(upfront, upfrontDate_));
×
137
            legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
×
138
            // backpayment on the floating leg
139
            // (accounts for non-par redemption, if any)
140
            Real backPayment = notional;
141
            ext::shared_ptr<CashFlow> backPaymentCashFlow(new
142
                SimpleCashFlow(backPayment, finalDate));
×
143
            legs_[1].push_back(backPaymentCashFlow);
×
144
        } else {
145
            // final notional exchange
146
            ext::shared_ptr<CashFlow> finalCashFlow (new
147
                SimpleCashFlow(notional, finalDate));
×
148
            legs_[1].push_back(finalCashFlow);
×
149
        }
150

151
        QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
×
152
        for (i=legs_[0].begin(); i<legs_[0].end(); ++i)
×
153
            registerWith(*i);
×
154

155
        if (payBondCoupon) {
×
156
            payer_[0]=-1.0;
×
157
            payer_[1]=+1.0;
×
158
        } else {
159
            payer_[0]=+1.0;
×
160
            payer_[1]=-1.0;
×
161
        }
162
    }
×
163

164
    AssetSwap::AssetSwap(bool payBondCoupon,
90✔
165
                         ext::shared_ptr<Bond> bond,
166
                         Real bondCleanPrice,
167
                         const ext::shared_ptr<IborIndex>& iborIndex,
168
                         Spread spread,
169
                         Schedule floatSchedule,
170
                         const DayCounter& floatingDayCounter,
171
                         bool parSwap)
90✔
172
    : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice), nonParRepayment_(100),
90✔
173
      spread_(spread), parSwap_(parSwap) {
90✔
174
        Schedule schedule = floatSchedule.empty()
175
            ? Schedule(bond_->settlementDate(),
176
                       bond_->maturityDate(),
90✔
177
                       iborIndex->tenor(),
90✔
178
                       iborIndex->fixingCalendar(),
90✔
179
                       iborIndex->businessDayConvention(),
180
                       iborIndex->businessDayConvention(),
181
                       DateGeneration::Backward,
182
                       false) // endOfMonth
183
            : std::move(floatSchedule);
270✔
184

185
        // the following might become an input parameter
186
        BusinessDayConvention paymentAdjustment = Following;
187

188
        Date finalDate = schedule.calendar().adjust(
90✔
189
            schedule.endDate(), paymentAdjustment);
190
        Date adjBondMaturityDate = schedule.calendar().adjust(
90✔
191
            bond_->maturityDate(), paymentAdjustment);
180✔
192

193
        QL_REQUIRE(finalDate==adjBondMaturityDate,
90✔
194
                   "adjusted schedule end date (" <<
195
                   finalDate <<
196
                   ") must be equal to adjusted bond maturity date (" <<
197
                   adjBondMaturityDate << ")");
198

199
        // bondCleanPrice must be the (forward) clean price
200
        // at the floating schedule start date
201
        upfrontDate_ = schedule.startDate();
90✔
202
        Real dirtyPrice = bondCleanPrice_ +
90✔
203
                          bond_->accruedAmount(upfrontDate_);
90✔
204

205
        Real notional = bond_->notional(upfrontDate_);
90✔
206
        /* In the market asset swap, the bond is purchased in return for
207
           payment of the full price. The notional of the floating leg is
208
           then scaled by the full price. */
209
        if (!parSwap_)
90✔
210
            notional *= dirtyPrice/100.0;
21✔
211

212
        if (floatingDayCounter==DayCounter())
180✔
NEW
213
            legs_[1] = IborLeg(std::move(schedule), iborIndex)
×
214
                .withNotionals(notional)
×
215
                .withPaymentAdjustment(paymentAdjustment)
×
216
                .withSpreads(spread);
×
217
        else
218
            legs_[1] = IborLeg(std::move(schedule), iborIndex)
180✔
219
                .withNotionals(notional)
90✔
220
                .withPaymentDayCounter(floatingDayCounter)
90✔
221
                .withPaymentAdjustment(paymentAdjustment)
90✔
222
                .withSpreads(spread);
180✔
223

224
        for (Leg::const_iterator i=legs_[1].begin(); i<legs_[1].end(); ++i)
2,930✔
225
            registerWith(*i);
5,680✔
226

227
        const Leg& bondLeg = bond_->cashflows();
90✔
228
        for (auto i = bondLeg.begin(); i < bondLeg.end(); ++i) {
1,690✔
229
            // whatever might be the choice for the discounting engine
230
            // bond flows on upfrontDate_ must be discarded
231
            bool upfrontDateBondFlows = false;
232
            if (!(*i)->hasOccurred(upfrontDate_, upfrontDateBondFlows))
1,600✔
233
                legs_[0].push_back(*i);
1,400✔
234
        }
235

236
        QL_REQUIRE(!legs_[0].empty(),
90✔
237
                   "empty bond leg to start with");
238

239
        // special flows
240
        if (parSwap_) {
90✔
241
            // upfront on the floating leg
242
            Real upfront = (dirtyPrice-100.0)/100.0*notional;
69✔
243
            ext::shared_ptr<CashFlow> upfrontCashFlow(new
244
                SimpleCashFlow(upfront, upfrontDate_));
69✔
245
            legs_[1].insert(legs_[1].begin(), upfrontCashFlow);
69✔
246
            // backpayment on the floating leg
247
            // (accounts for non-par redemption, if any)
248
            Real backPayment = notional;
249
            ext::shared_ptr<CashFlow> backPaymentCashFlow(new
250
                SimpleCashFlow(backPayment, finalDate));
69✔
251
            legs_[1].push_back(backPaymentCashFlow);
69✔
252
        } else {
253
            // final notional exchange
254
            ext::shared_ptr<CashFlow> finalCashFlow(new
255
                SimpleCashFlow(notional, finalDate));
21✔
256
            legs_[1].push_back(finalCashFlow);
21✔
257
        }
258

259
        QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
90✔
260
        for (Leg::const_iterator i=legs_[0].begin(); i<legs_[0].end(); ++i)
1,490✔
261
            registerWith(*i);
2,800✔
262

263
        if (payBondCoupon) {
90✔
264
            payer_[0]=-1.0;
90✔
265
            payer_[1]=+1.0;
90✔
266
        } else {
267
            payer_[0]=+1.0;
×
268
            payer_[1]=-1.0;
×
269
        }
270
    }
90✔
271

272
    void AssetSwap::setupArguments(PricingEngine::arguments* args) const {
92✔
273

274
        Swap::setupArguments(args);
92✔
275

276
        auto* arguments = dynamic_cast<AssetSwap::arguments*>(args);
92✔
277

278
        if (arguments == nullptr) // it's a swap engine...
92✔
279
            return;
280

281
        const Leg& fixedCoupons = bondLeg();
282

283
        arguments->fixedResetDates = arguments->fixedPayDates =
284
            vector<Date>(fixedCoupons.size());
×
285
        arguments->fixedCoupons = vector<Real>(fixedCoupons.size());
×
286

287
        for (Size i=0; i<fixedCoupons.size(); ++i) {
×
288
            ext::shared_ptr<FixedRateCoupon> coupon =
289
                ext::dynamic_pointer_cast<FixedRateCoupon>(fixedCoupons[i]);
×
290

291
            arguments->fixedPayDates[i] = coupon->date();
×
292
            arguments->fixedResetDates[i] = coupon->accrualStartDate();
×
293
            arguments->fixedCoupons[i] = coupon->amount();
×
294
        }
295

296
        const Leg& floatingCoupons = floatingLeg();
297

298
        arguments->floatingResetDates = arguments->floatingPayDates =
299
            arguments->floatingFixingDates =
300
            vector<Date>(floatingCoupons.size());
×
301
        arguments->floatingAccrualTimes =
302
            vector<Time>(floatingCoupons.size());
×
303
        arguments->floatingSpreads =
304
            vector<Spread>(floatingCoupons.size());
×
305

306
        for (Size i=0; i<floatingCoupons.size(); ++i) {
×
307
            ext::shared_ptr<FloatingRateCoupon> coupon =
308
                ext::dynamic_pointer_cast<FloatingRateCoupon>(floatingCoupons[i]);
×
309

310
            arguments->floatingResetDates[i] = coupon->accrualStartDate();
×
311
            arguments->floatingPayDates[i] = coupon->date();
×
312
            arguments->floatingFixingDates[i] = coupon->fixingDate();
×
313
            arguments->floatingAccrualTimes[i] = coupon->accrualPeriod();
×
314
            arguments->floatingSpreads[i] = coupon->spread();
×
315
        }
316
    }
317

318
    Spread AssetSwap::fairSpread() const {
60✔
319
        static const Spread basisPoint = 1.0e-4;
320
        calculate();
60✔
321
        if (fairSpread_ != Null<Spread>()) {
60✔
322
            return fairSpread_;
323
        } else if (legBPS_.size() > 1 && legBPS_[1] != Null<Spread>()) {
60✔
324
            fairSpread_ = spread_ - NPV_/legBPS_[1]*basisPoint;
60✔
325
            return fairSpread_;
60✔
326
        } else {
327
            QL_FAIL("fair spread not available");
×
328
        }
329
    }
330

331
    Real AssetSwap::floatingLegBPS() const {
×
332
        calculate();
×
333
        QL_REQUIRE(legBPS_.size() > 1 && legBPS_[1] != Null<Real>(),
×
334
                   "floating-leg BPS not available");
335
        return legBPS_[1];
×
336
    }
337

338
    Real AssetSwap::floatingLegNPV() const {
×
339
        calculate();
×
340
        QL_REQUIRE(legNPV_.size() > 1 && legNPV_[1] != Null<Real>(),
×
341
                   "floating-leg NPV not available");
342
        return legNPV_[1];
×
343
    }
344

345
    Real AssetSwap::fairCleanPrice() const {
44✔
346
        calculate();
44✔
347
        if (fairCleanPrice_ != Null<Real>()) {
44✔
348
            return fairCleanPrice_;
349
        } else {
350
            QL_REQUIRE(startDiscounts_[1]!=Null<DiscountFactor>(),
44✔
351
                       "fair clean price not available for seasoned deal");
352
            Real notional = bond_->notional(upfrontDate_);
44✔
353
            if (parSwap_) {
44✔
354
                fairCleanPrice_ = bondCleanPrice_ - payer_[1] *
38✔
355
                    NPV_*npvDateDiscount_/startDiscounts_[1]/(notional/100.0);
38✔
356
            } else {
357
                Real accruedAmount = bond_->accruedAmount(upfrontDate_);
6✔
358
                Real dirtyPrice = bondCleanPrice_ + accruedAmount;
6✔
359
                Real fairDirtyPrice = - legNPV_[0]/legNPV_[1] * dirtyPrice;
6✔
360
                fairCleanPrice_ = fairDirtyPrice - accruedAmount;
6✔
361
            }
362

363
            return fairCleanPrice_;
44✔
364
        }
365
    }
366

367
    Real AssetSwap::fairNonParRepayment() const {
×
368
        calculate();
×
369
        if (fairNonParRepayment_ != Null<Real>()) {
×
370
            return fairNonParRepayment_;
371
        } else {
372
            QL_REQUIRE(endDiscounts_[1]!=Null<DiscountFactor>(),
×
373
                       "fair non par repayment not available for expired leg");
374
            Real notional = bond_->notional(upfrontDate_);
×
NEW
375
            fairNonParRepayment_ = nonParRepayment_ - payer_[0] *
×
376
                NPV_*npvDateDiscount_/endDiscounts_[1]/(notional/100.0);
×
377
            return fairNonParRepayment_;
×
378
        }
379
    }
380

381
    void AssetSwap::setupExpired() const {
×
382
        Swap::setupExpired();
×
383
        fairSpread_ = Null<Spread>();
×
384
        fairCleanPrice_ = Null<Real>();
×
385
        fairNonParRepayment_ = Null<Real>();
×
386
    }
×
387

388
    void AssetSwap::fetchResults(const PricingEngine::results* r) const {
92✔
389
        Swap::fetchResults(r);
92✔
390
        const auto* results = dynamic_cast<const AssetSwap::results*>(r);
92✔
391
        if (results != nullptr) {
92✔
392
            fairSpread_ = results->fairSpread;
×
393
            fairCleanPrice_= results->fairCleanPrice;
×
394
            fairNonParRepayment_= results->fairNonParRepayment;
×
395
        } else {
396
            fairSpread_ = Null<Spread>();
92✔
397
            fairCleanPrice_ = Null<Real>();
92✔
398
            fairNonParRepayment_ = Null<Real>();
92✔
399
        }
400
    }
92✔
401

402
    void AssetSwap::arguments::validate() const {
×
403
        QL_REQUIRE(fixedResetDates.size() == fixedPayDates.size(),
×
404
                   "number of fixed start dates different from "
405
                   "number of fixed payment dates");
406
        QL_REQUIRE(fixedPayDates.size() == fixedCoupons.size(),
×
407
                   "number of fixed payment dates different from "
408
                   "number of fixed coupon amounts");
409
        QL_REQUIRE(floatingResetDates.size() == floatingPayDates.size(),
×
410
                   "number of floating start dates different from "
411
                   "number of floating payment dates");
412
        QL_REQUIRE(floatingFixingDates.size() == floatingPayDates.size(),
×
413
                   "number of floating fixing dates different from "
414
                   "number of floating payment dates");
415
        QL_REQUIRE(floatingAccrualTimes.size() == floatingPayDates.size(),
×
416
                   "number of floating accrual times different from "
417
                   "number of floating payment dates");
418
        QL_REQUIRE(floatingSpreads.size() == floatingPayDates.size(),
×
419
                   "number of floating spreads different from "
420
                   "number of floating payment dates");
421
    }
×
422

423
    void AssetSwap::results::reset() {
×
424
        Swap::results::reset();
×
425
        fairSpread = Null<Spread>();
×
426
        fairCleanPrice = Null<Real>();
×
427
        fairNonParRepayment = Null<Real>();
×
428
    }
×
429

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

© 2026 Coveralls, Inc