/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 | | } |