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
31using std::vector;
32
33namespace 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, convention: paymentAdjustment);
71 Schedule schedule = tempSch.until(truncationDate: 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(d: upfrontDate_);
78
79 Real notional = bond_->notional(d: 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(h: *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(refDate: upfrontDate_, includeRefDate: upfrontDateBondFlows))
111 legs_[0].push_back(x: *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>(r: *i);
117 if (c != nullptr) {
118 ext::shared_ptr<CashFlow> accruedCoupon(new
119 SimpleCashFlow(c->accruedAmount(dealMaturity), finalDate));
120 legs_[0].push_back(x: accruedCoupon);
121 }
122 }
123 // add the nonParRepayment_
124 ext::shared_ptr<CashFlow> nonParRepaymentFlow(new
125 SimpleCashFlow(nonParRepayment_, finalDate));
126 legs_[0].push_back(x: 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(position: legs_[1].begin(), x: 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(x: backPaymentCashFlow);
144 } else {
145 // final notional exchange
146 ext::shared_ptr<CashFlow> finalCashFlow (new
147 SimpleCashFlow(notional, finalDate));
148 legs_[1].push_back(x: 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(h: *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,
165 ext::shared_ptr<Bond> bond,
166 Real bondCleanPrice,
167 const ext::shared_ptr<IborIndex>& iborIndex,
168 Spread spread,
169 const Schedule& floatSchedule,
170 const DayCounter& floatingDayCounter,
171 bool parSwap)
172 : Swap(2), bond_(std::move(bond)), bondCleanPrice_(bondCleanPrice), nonParRepayment_(100),
173 spread_(spread), parSwap_(parSwap) {
174 Schedule schedule = floatSchedule;
175 if (floatSchedule.empty())
176 schedule = Schedule(bond_->settlementDate(),
177 bond_->maturityDate(),
178 iborIndex->tenor(),
179 iborIndex->fixingCalendar(),
180 iborIndex->businessDayConvention(),
181 iborIndex->businessDayConvention(),
182 DateGeneration::Backward,
183 false); // endOfMonth
184
185 // the following might become an input parameter
186 BusinessDayConvention paymentAdjustment = Following;
187
188 Date finalDate = schedule.calendar().adjust(
189 schedule.endDate(), convention: paymentAdjustment);
190 Date adjBondMaturityDate = schedule.calendar().adjust(
191 bond_->maturityDate(), convention: paymentAdjustment);
192
193 QL_REQUIRE(finalDate==adjBondMaturityDate,
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();
202 Real dirtyPrice = bondCleanPrice_ +
203 bond_->accruedAmount(d: upfrontDate_);
204
205 Real notional = bond_->notional(d: upfrontDate_);
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_)
210 notional *= dirtyPrice/100.0;
211
212 if (floatingDayCounter==DayCounter())
213 legs_[1] = IborLeg(schedule, iborIndex)
214 .withNotionals(notional)
215 .withPaymentAdjustment(paymentAdjustment)
216 .withSpreads(spread);
217 else
218 legs_[1] = IborLeg(schedule, iborIndex)
219 .withNotionals(notional)
220 .withPaymentDayCounter(floatingDayCounter)
221 .withPaymentAdjustment(paymentAdjustment)
222 .withSpreads(spread);
223
224 for (Leg::const_iterator i=legs_[1].begin(); i<legs_[1].end(); ++i)
225 registerWith(h: *i);
226
227 const Leg& bondLeg = bond_->cashflows();
228 for (auto i = bondLeg.begin(); i < bondLeg.end(); ++i) {
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(refDate: upfrontDate_, includeRefDate: upfrontDateBondFlows))
233 legs_[0].push_back(x: *i);
234 }
235
236 QL_REQUIRE(!legs_[0].empty(),
237 "empty bond leg to start with");
238
239 // special flows
240 if (parSwap_) {
241 // upfront on the floating leg
242 Real upfront = (dirtyPrice-100.0)/100.0*notional;
243 ext::shared_ptr<CashFlow> upfrontCashFlow(new
244 SimpleCashFlow(upfront, upfrontDate_));
245 legs_[1].insert(position: legs_[1].begin(), x: upfrontCashFlow);
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));
251 legs_[1].push_back(x: backPaymentCashFlow);
252 } else {
253 // final notional exchange
254 ext::shared_ptr<CashFlow> finalCashFlow(new
255 SimpleCashFlow(notional, finalDate));
256 legs_[1].push_back(x: finalCashFlow);
257 }
258
259 QL_REQUIRE(!legs_[0].empty(), "empty bond leg");
260 for (Leg::const_iterator i=legs_[0].begin(); i<legs_[0].end(); ++i)
261 registerWith(h: *i);
262
263 if (payBondCoupon) {
264 payer_[0]=-1.0;
265 payer_[1]=+1.0;
266 } else {
267 payer_[0]=+1.0;
268 payer_[1]=-1.0;
269 }
270 }
271
272 void AssetSwap::setupArguments(PricingEngine::arguments* args) const {
273
274 Swap::setupArguments(args);
275
276 auto* arguments = dynamic_cast<AssetSwap::arguments*>(args);
277
278 if (arguments == nullptr) // it's a swap engine...
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>(r: 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>(r: 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 {
319 static const Spread basisPoint = 1.0e-4;
320 calculate();
321 if (fairSpread_ != Null<Spread>()) {
322 return fairSpread_;
323 } else if (legBPS_.size() > 1 && legBPS_[1] != Null<Spread>()) {
324 fairSpread_ = spread_ - NPV_/legBPS_[1]*basisPoint;
325 return fairSpread_;
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 {
346 calculate();
347 if (fairCleanPrice_ != Null<Real>()) {
348 return fairCleanPrice_;
349 } else {
350 QL_REQUIRE(startDiscounts_[1]!=Null<DiscountFactor>(),
351 "fair clean price not available for seasoned deal");
352 Real notional = bond_->notional(d: upfrontDate_);
353 if (parSwap_) {
354 fairCleanPrice_ = bondCleanPrice_ - payer_[1] *
355 NPV_*npvDateDiscount_/startDiscounts_[1]/(notional/100.0);
356 } else {
357 Real accruedAmount = bond_->accruedAmount(d: upfrontDate_);
358 Real dirtyPrice = bondCleanPrice_ + accruedAmount;
359 Real fairDirtyPrice = - legNPV_[0]/legNPV_[1] * dirtyPrice;
360 fairCleanPrice_ = fairDirtyPrice - accruedAmount;
361 }
362
363 return fairCleanPrice_;
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(d: upfrontDate_);
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 {
389 Swap::fetchResults(r);
390 const auto* results = dynamic_cast<const AssetSwap::results*>(r);
391 if (results != nullptr) {
392 fairSpread_ = results->fairSpread;
393 fairCleanPrice_= results->fairCleanPrice;
394 fairNonParRepayment_= results->fairNonParRepayment;
395 } else {
396 fairSpread_ = Null<Spread>();
397 fairCleanPrice_ = Null<Real>();
398 fairNonParRepayment_ = Null<Real>();
399 }
400 }
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}
431

source code of quantlib/ql/instruments/assetswap.cpp

Morty Proxy This is a proxified and sanitized view of the page, visit original site.