| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2009, 2012 StatPro Italia srl |
| 5 | |
| 6 | This file is part of QuantLib, a free-software/open-source library |
| 7 | for financial quantitative analysts and developers - http://quantlib.org/ |
| 8 | |
| 9 | QuantLib is free software: you can redistribute it and/or modify it |
| 10 | under the terms of the QuantLib license. You should have received a |
| 11 | copy of the license along with this program; if not, please email |
| 12 | <quantlib-dev@lists.sf.net>. The license is also available online at |
| 13 | <http://quantlib.org/license.shtml>. |
| 14 | |
| 15 | This program is distributed in the hope that it will be useful, but WITHOUT |
| 16 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
| 17 | FOR A PARTICULAR PURPOSE. See the license for more details. |
| 18 | */ |
| 19 | |
| 20 | #include "cashflows.hpp" |
| 21 | #include "utilities.hpp" |
| 22 | #include <ql/cashflows/cashflows.hpp> |
| 23 | #include <ql/cashflows/simplecashflow.hpp> |
| 24 | #include <ql/cashflows/fixedratecoupon.hpp> |
| 25 | #include <ql/cashflows/floatingratecoupon.hpp> |
| 26 | #include <ql/cashflows/iborcoupon.hpp> |
| 27 | #include <ql/cashflows/overnightindexedcoupon.hpp> |
| 28 | #include <ql/cashflows/couponpricer.hpp> |
| 29 | #include <ql/termstructures/volatility/optionlet/constantoptionletvol.hpp> |
| 30 | #include <ql/quotes/simplequote.hpp> |
| 31 | #include <ql/time/calendars/target.hpp> |
| 32 | #include <ql/time/daycounters/actualactual.hpp> |
| 33 | #include <ql/time/schedule.hpp> |
| 34 | #include <ql/indexes/ibor/euribor.hpp> |
| 35 | #include <ql/indexes/ibor/usdlibor.hpp> |
| 36 | #include <ql/indexes/ibor/sofr.hpp> |
| 37 | #include <ql/optional.hpp> |
| 38 | #include <ql/settings.hpp> |
| 39 | #include <iomanip> |
| 40 | |
| 41 | using namespace QuantLib; |
| 42 | using namespace boost::unit_test_framework; |
| 43 | |
| 44 | void CashFlowsTest::testSettings() { |
| 45 | |
| 46 | BOOST_TEST_MESSAGE("Testing cash-flow settings..." ); |
| 47 | |
| 48 | Date today = Date::todaysDate(); |
| 49 | Settings::instance().evaluationDate() = today; |
| 50 | |
| 51 | // cash flows at T+0, T+1, T+2 |
| 52 | std::vector<ext::shared_ptr<CashFlow> > leg; |
| 53 | leg.reserve(n: 3); |
| 54 | for (Integer i = 0; i < 3; ++i) |
| 55 | leg.push_back(x: ext::shared_ptr<CashFlow>(new SimpleCashFlow(1.0, today+i))); |
| 56 | |
| 57 | |
| 58 | #define CHECK_INCLUSION(n, days, expected) \ |
| 59 | if ((!leg[n]->hasOccurred(today+days)) != expected) { \ |
| 60 | BOOST_ERROR("cashflow at T+" << n << " " \ |
| 61 | << (expected ? "not" : "") << "included" \ |
| 62 | << " at T+" << days); \ |
| 63 | } |
| 64 | |
| 65 | // case 1: don't include reference-date payments, no override at |
| 66 | // today's date |
| 67 | |
| 68 | Settings::instance().includeReferenceDateEvents() = false; |
| 69 | Settings::instance().includeTodaysCashFlows() = ext::nullopt; |
| 70 | |
| 71 | CHECK_INCLUSION(0, 0, false); |
| 72 | CHECK_INCLUSION(0, 1, false); |
| 73 | |
| 74 | CHECK_INCLUSION(1, 0, true); |
| 75 | CHECK_INCLUSION(1, 1, false); |
| 76 | CHECK_INCLUSION(1, 2, false); |
| 77 | |
| 78 | CHECK_INCLUSION(2, 1, true); |
| 79 | CHECK_INCLUSION(2, 2, false); |
| 80 | CHECK_INCLUSION(2, 3, false); |
| 81 | |
| 82 | // case 2: same, but with explicit setting at today's date |
| 83 | |
| 84 | Settings::instance().includeReferenceDateEvents() = false; |
| 85 | Settings::instance().includeTodaysCashFlows() = false; |
| 86 | |
| 87 | CHECK_INCLUSION(0, 0, false); |
| 88 | CHECK_INCLUSION(0, 1, false); |
| 89 | |
| 90 | CHECK_INCLUSION(1, 0, true); |
| 91 | CHECK_INCLUSION(1, 1, false); |
| 92 | CHECK_INCLUSION(1, 2, false); |
| 93 | |
| 94 | CHECK_INCLUSION(2, 1, true); |
| 95 | CHECK_INCLUSION(2, 2, false); |
| 96 | CHECK_INCLUSION(2, 3, false); |
| 97 | |
| 98 | // case 3: do include reference-date payments, no override at |
| 99 | // today's date |
| 100 | |
| 101 | Settings::instance().includeReferenceDateEvents() = true; |
| 102 | Settings::instance().includeTodaysCashFlows() = ext::nullopt; |
| 103 | |
| 104 | CHECK_INCLUSION(0, 0, true); |
| 105 | CHECK_INCLUSION(0, 1, false); |
| 106 | |
| 107 | CHECK_INCLUSION(1, 0, true); |
| 108 | CHECK_INCLUSION(1, 1, true); |
| 109 | CHECK_INCLUSION(1, 2, false); |
| 110 | |
| 111 | CHECK_INCLUSION(2, 1, true); |
| 112 | CHECK_INCLUSION(2, 2, true); |
| 113 | CHECK_INCLUSION(2, 3, false); |
| 114 | |
| 115 | // case 4: do include reference-date payments, explicit (and same) |
| 116 | // setting at today's date |
| 117 | |
| 118 | Settings::instance().includeReferenceDateEvents() = true; |
| 119 | Settings::instance().includeTodaysCashFlows() = true; |
| 120 | |
| 121 | CHECK_INCLUSION(0, 0, true); |
| 122 | CHECK_INCLUSION(0, 1, false); |
| 123 | |
| 124 | CHECK_INCLUSION(1, 0, true); |
| 125 | CHECK_INCLUSION(1, 1, true); |
| 126 | CHECK_INCLUSION(1, 2, false); |
| 127 | |
| 128 | CHECK_INCLUSION(2, 1, true); |
| 129 | CHECK_INCLUSION(2, 2, true); |
| 130 | CHECK_INCLUSION(2, 3, false); |
| 131 | |
| 132 | // case 5: do include reference-date payments, override at |
| 133 | // today's date |
| 134 | |
| 135 | Settings::instance().includeReferenceDateEvents() = true; |
| 136 | Settings::instance().includeTodaysCashFlows() = false; |
| 137 | |
| 138 | CHECK_INCLUSION(0, 0, false); |
| 139 | CHECK_INCLUSION(0, 1, false); |
| 140 | |
| 141 | CHECK_INCLUSION(1, 0, true); |
| 142 | CHECK_INCLUSION(1, 1, true); |
| 143 | CHECK_INCLUSION(1, 2, false); |
| 144 | |
| 145 | CHECK_INCLUSION(2, 1, true); |
| 146 | CHECK_INCLUSION(2, 2, true); |
| 147 | CHECK_INCLUSION(2, 3, false); |
| 148 | |
| 149 | |
| 150 | // no discount to make calculations easier |
| 151 | InterestRate no_discount(0.0, Actual365Fixed(), Continuous, Annual); |
| 152 | |
| 153 | #define CHECK_NPV(includeRef, expected) \ |
| 154 | do { \ |
| 155 | Real NPV = CashFlows::npv(leg, no_discount, includeRef, today); \ |
| 156 | if (std::fabs(NPV - expected) > 1e-6) { \ |
| 157 | BOOST_ERROR("NPV mismatch:\n" \ |
| 158 | << " calculated: " << NPV << "\n" \ |
| 159 | << " expected: " << expected); \ |
| 160 | } \ |
| 161 | } while (false); |
| 162 | |
| 163 | // no override |
| 164 | Settings::instance().includeTodaysCashFlows() = ext::nullopt; |
| 165 | |
| 166 | CHECK_NPV(false, 2.0); |
| 167 | CHECK_NPV(true, 3.0); |
| 168 | |
| 169 | // override |
| 170 | Settings::instance().includeTodaysCashFlows() = false; |
| 171 | |
| 172 | CHECK_NPV(false, 2.0); |
| 173 | CHECK_NPV(true, 2.0); |
| 174 | |
| 175 | } |
| 176 | |
| 177 | void CashFlowsTest::testAccessViolation() { |
| 178 | BOOST_TEST_MESSAGE("Testing dynamic cast of coupon in Black pricer..." ); |
| 179 | |
| 180 | Date todaysDate(7, April, 2010); |
| 181 | Date settlementDate(9, April, 2010); |
| 182 | Settings::instance().evaluationDate() = todaysDate; |
| 183 | Calendar calendar = TARGET(); |
| 184 | |
| 185 | Handle<YieldTermStructure> rhTermStructure( |
| 186 | flatRate(today: settlementDate, forward: 0.04875825, dc: Actual365Fixed())); |
| 187 | |
| 188 | Volatility volatility = 0.10; |
| 189 | Handle<OptionletVolatilityStructure> vol; |
| 190 | vol = Handle<OptionletVolatilityStructure>( |
| 191 | ext::shared_ptr<OptionletVolatilityStructure>( |
| 192 | new ConstantOptionletVolatility( |
| 193 | 2, |
| 194 | calendar, |
| 195 | ModifiedFollowing, |
| 196 | volatility, |
| 197 | Actual365Fixed()))); |
| 198 | |
| 199 | ext::shared_ptr<IborIndex> index3m (new USDLibor(3*Months, |
| 200 | rhTermStructure)); |
| 201 | |
| 202 | Date payDate(20, December, 2013); |
| 203 | Date startDate(20, September, 2013); |
| 204 | Date endDate(20, December, 2013); |
| 205 | Rate spread = 0.0115; |
| 206 | ext::shared_ptr<IborCouponPricer> pricer(new BlackIborCouponPricer(vol)); |
| 207 | ext::shared_ptr<FloatingRateCoupon> coupon( |
| 208 | new FloatingRateCoupon(payDate,100, startDate, endDate, 2, |
| 209 | index3m, 1.0 , spread / 100)); |
| 210 | coupon->setPricer(pricer); |
| 211 | |
| 212 | try { |
| 213 | // this caused an access violation in version 1.0 |
| 214 | coupon->amount(); |
| 215 | } catch (Error&) { |
| 216 | // ok; proper exception thrown |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | void CashFlowsTest::testDefaultSettlementDate() { |
| 221 | BOOST_TEST_MESSAGE("Testing default evaluation date in cashflows methods..." ); |
| 222 | Date today = Settings::instance().evaluationDate(); |
| 223 | Schedule schedule = |
| 224 | MakeSchedule() |
| 225 | .from(effectiveDate: today-2*Months).to(terminationDate: today+4*Months) |
| 226 | .withFrequency(Semiannual) |
| 227 | .withCalendar(TARGET()) |
| 228 | .withConvention(Unadjusted) |
| 229 | .backwards(); |
| 230 | |
| 231 | Leg leg = FixedRateLeg(schedule) |
| 232 | .withNotionals(100.0) |
| 233 | .withCouponRates(0.03, paymentDayCounter: Actual360()) |
| 234 | .withPaymentCalendar(TARGET()) |
| 235 | .withPaymentAdjustment(Following); |
| 236 | |
| 237 | Time accruedPeriod = CashFlows::accruedPeriod(leg, includeSettlementDateFlows: false); |
| 238 | if (accruedPeriod == 0.0) |
| 239 | BOOST_ERROR("null accrued period with default settlement date" ); |
| 240 | |
| 241 | Date::serial_type accruedDays = CashFlows::accruedDays(leg, includeSettlementDateFlows: false); |
| 242 | if (accruedDays == 0) |
| 243 | BOOST_ERROR("no accrued days with default settlement date" ); |
| 244 | |
| 245 | Real accruedAmount = CashFlows::accruedAmount(leg, includeSettlementDateFlows: false); |
| 246 | if (accruedAmount == 0.0) |
| 247 | BOOST_ERROR("null accrued amount with default settlement date" ); |
| 248 | } |
| 249 | |
| 250 | void CashFlowsTest::testNullFixingDays() { |
| 251 | BOOST_TEST_MESSAGE("Testing ibor leg construction with null fixing days..." ); |
| 252 | Date today = Settings::instance().evaluationDate(); |
| 253 | Schedule schedule = |
| 254 | MakeSchedule() |
| 255 | .from(effectiveDate: today-2*Months).to(terminationDate: today+4*Months) |
| 256 | .withFrequency(Semiannual) |
| 257 | .withCalendar(TARGET()) |
| 258 | .withConvention(Following) |
| 259 | .backwards(); |
| 260 | |
| 261 | ext::shared_ptr<IborIndex> index(new USDLibor(6*Months)); |
| 262 | Leg leg = IborLeg(schedule, index) |
| 263 | .withNotionals(notional: 100.0) |
| 264 | // this can happen with default values, and caused an |
| 265 | // exception when the null was not managed properly |
| 266 | .withFixingDays(fixingDays: Null<Natural>()); |
| 267 | } |
| 268 | |
| 269 | void CashFlowsTest::testExCouponDates() { |
| 270 | BOOST_TEST_MESSAGE("Testing ex-coupon date calculation..." ); |
| 271 | |
| 272 | Date today = Date::todaysDate(); |
| 273 | Schedule schedule = MakeSchedule() |
| 274 | .from(effectiveDate: today) |
| 275 | .to(terminationDate: today + 5 * Years) |
| 276 | .withFrequency(Monthly) |
| 277 | .withCalendar(TARGET()) |
| 278 | .withConvention(Following); |
| 279 | |
| 280 | // no ex-coupon dates |
| 281 | Leg l1 = FixedRateLeg(schedule).withNotionals(100.0).withCouponRates(0.03, paymentDayCounter: Actual360()); |
| 282 | for (auto& i : l1) { |
| 283 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 284 | if (c->exCouponDate() != Date()) { |
| 285 | BOOST_ERROR("ex-coupon date found (none expected)" ); |
| 286 | } |
| 287 | } |
| 288 | |
| 289 | // same for floating legs |
| 290 | ext::shared_ptr<IborIndex> index(new Euribor3M); |
| 291 | Leg l2 = IborLeg(schedule, index).withNotionals(notional: 100.0); |
| 292 | for (auto& i : l2) { |
| 293 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 294 | if (c->exCouponDate() != Date()) { |
| 295 | BOOST_ERROR("ex-coupon date found (none expected)" ); |
| 296 | } |
| 297 | } |
| 298 | |
| 299 | // calendar days |
| 300 | Leg l5 = FixedRateLeg(schedule) |
| 301 | .withNotionals(100.0) |
| 302 | .withCouponRates(0.03, paymentDayCounter: Actual360()) |
| 303 | .withExCouponPeriod(Period(2, Days), NullCalendar(), Unadjusted, endOfMonth: false); |
| 304 | for (auto& i : l5) { |
| 305 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 306 | Date expected = c->accrualEndDate() - 2; |
| 307 | if (c->exCouponDate() != expected) { |
| 308 | BOOST_ERROR("ex-coupon date = " << c->exCouponDate() << " (" << expected |
| 309 | << " expected)" ); |
| 310 | } |
| 311 | } |
| 312 | |
| 313 | Leg l6 = IborLeg(schedule, index) |
| 314 | .withNotionals(notional: 100.0) |
| 315 | .withExCouponPeriod(Period(2, Days), NullCalendar(), Unadjusted, endOfMonth: false); |
| 316 | for (auto& i : l6) { |
| 317 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 318 | Date expected = c->accrualEndDate() - 2; |
| 319 | if (c->exCouponDate() != expected) { |
| 320 | BOOST_ERROR("ex-coupon date = " << c->exCouponDate() << " (" << expected |
| 321 | << " expected)" ); |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | // business days |
| 326 | Leg l7 = FixedRateLeg(schedule) |
| 327 | .withNotionals(100.0) |
| 328 | .withCouponRates(0.03, paymentDayCounter: Actual360()) |
| 329 | .withExCouponPeriod(Period(2, Days), TARGET(), Preceding, endOfMonth: false); |
| 330 | for (auto& i : l7) { |
| 331 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 332 | Date expected = TARGET().advance(c->accrualEndDate(), n: -2, unit: Days); |
| 333 | if (c->exCouponDate() != expected) { |
| 334 | BOOST_ERROR("ex-coupon date = " << c->exCouponDate() << " (" << expected |
| 335 | << " expected)" ); |
| 336 | } |
| 337 | } |
| 338 | |
| 339 | Leg l8 = IborLeg(schedule, index) |
| 340 | .withNotionals(notional: 100.0) |
| 341 | .withExCouponPeriod(Period(2, Days), TARGET(), Preceding, endOfMonth: false); |
| 342 | for (auto& i : l8) { |
| 343 | ext::shared_ptr<Coupon> c = ext::dynamic_pointer_cast<Coupon>(r: i); |
| 344 | Date expected = TARGET().advance(c->accrualEndDate(), n: -2, unit: Days); |
| 345 | if (c->exCouponDate() != expected) { |
| 346 | BOOST_ERROR("ex-coupon date = " << c->exCouponDate() << " (" << expected |
| 347 | << " expected)" ); |
| 348 | } |
| 349 | } |
| 350 | } |
| 351 | |
| 352 | void CashFlowsTest::testIrregularFirstCouponReferenceDatesAtEndOfMonth() { |
| 353 | BOOST_TEST_MESSAGE("Testing irregular first coupon reference dates with end of month enabled..." ); |
| 354 | Schedule schedule = |
| 355 | MakeSchedule() |
| 356 | .from(effectiveDate: Date(17, January, 2017)).to(terminationDate: Date(28, February, 2018)) |
| 357 | .withFrequency(Semiannual) |
| 358 | .withConvention(Unadjusted) |
| 359 | .endOfMonth() |
| 360 | .backwards(); |
| 361 | |
| 362 | Leg leg = FixedRateLeg(schedule) |
| 363 | .withNotionals(100.0) |
| 364 | .withCouponRates(0.01, paymentDayCounter: Actual360()); |
| 365 | |
| 366 | ext::shared_ptr<Coupon> firstCoupon = |
| 367 | ext::dynamic_pointer_cast<Coupon>(r: leg.front()); |
| 368 | |
| 369 | if (firstCoupon->referencePeriodStart() != Date(31, August, 2016)) |
| 370 | BOOST_ERROR("Expected reference start date at end of month, " |
| 371 | "got " << firstCoupon->referencePeriodStart()); |
| 372 | } |
| 373 | |
| 374 | void CashFlowsTest::testIrregularLastCouponReferenceDatesAtEndOfMonth() { |
| 375 | BOOST_TEST_MESSAGE("Testing irregular last coupon reference dates with end of month enabled..." ); |
| 376 | Schedule schedule = |
| 377 | MakeSchedule() |
| 378 | .from(effectiveDate: Date(17, January, 2017)).to(terminationDate: Date(15, September, 2018)) |
| 379 | .withNextToLastDate(d: Date(28, February, 2018)) |
| 380 | .withFrequency(Semiannual) |
| 381 | .withConvention(Unadjusted) |
| 382 | .endOfMonth() |
| 383 | .backwards(); |
| 384 | |
| 385 | Leg leg = FixedRateLeg(schedule) |
| 386 | .withNotionals(100.0) |
| 387 | .withCouponRates(0.01, paymentDayCounter: Actual360()); |
| 388 | |
| 389 | ext::shared_ptr<Coupon> lastCoupon = |
| 390 | ext::dynamic_pointer_cast<Coupon>(r: leg.back()); |
| 391 | |
| 392 | if (lastCoupon->referencePeriodEnd() != Date(31, August, 2018)) |
| 393 | BOOST_ERROR("Expected reference end date at end of month, " |
| 394 | "got " << lastCoupon->referencePeriodEnd()); |
| 395 | } |
| 396 | |
| 397 | void CashFlowsTest::testPartialScheduleLegConstruction() { |
| 398 | BOOST_TEST_MESSAGE("Testing leg construction with partial schedule..." ); |
| 399 | // schedule with irregular first and last period |
| 400 | Schedule schedule = MakeSchedule() |
| 401 | .from(effectiveDate: Date(15, September, 2017)) |
| 402 | .to(terminationDate: Date(30, September, 2020)) |
| 403 | .withNextToLastDate(d: Date(25, September, 2020)) |
| 404 | .withFrequency(Semiannual) |
| 405 | .backwards(); |
| 406 | // same schedule, date based, with metadata |
| 407 | Schedule schedule2(schedule.dates(), NullCalendar(), Unadjusted, Unadjusted, |
| 408 | 6 * Months, ext::nullopt, schedule.endOfMonth(), |
| 409 | schedule.isRegular()); |
| 410 | // same schedule, date based, without metadata |
| 411 | Schedule schedule3(schedule.dates()); |
| 412 | |
| 413 | // fixed rate legs based on the three schedule |
| 414 | Leg leg = FixedRateLeg(schedule).withNotionals(100.0).withCouponRates( |
| 415 | 0.01, paymentDayCounter: ActualActual(ActualActual::ISMA)); |
| 416 | Leg leg2 = FixedRateLeg(schedule2).withNotionals(100.0).withCouponRates( |
| 417 | 0.01, paymentDayCounter: ActualActual(ActualActual::ISMA)); |
| 418 | Leg leg3 = FixedRateLeg(schedule3).withNotionals(100.0).withCouponRates( |
| 419 | 0.01, paymentDayCounter: ActualActual(ActualActual::ISMA)); |
| 420 | |
| 421 | // check reference period of first and last coupon in all variants |
| 422 | // for the first two we expect a 6M reference period, for the |
| 423 | // third it can not be constructed, so should be equal to the |
| 424 | // respective schedule period |
| 425 | ext::shared_ptr<FixedRateCoupon> firstCpn = |
| 426 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg.front()); |
| 427 | ext::shared_ptr<FixedRateCoupon> lastCpn = |
| 428 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg.back()); |
| 429 | BOOST_REQUIRE(firstCpn != nullptr); |
| 430 | BOOST_REQUIRE(lastCpn != nullptr); |
| 431 | BOOST_CHECK_EQUAL(firstCpn->referencePeriodStart(), Date(25, Mar, 2017)); |
| 432 | BOOST_CHECK_EQUAL(firstCpn->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 433 | BOOST_CHECK_EQUAL(lastCpn->referencePeriodStart(), Date(25, Sep, 2020)); |
| 434 | BOOST_CHECK_EQUAL(lastCpn->referencePeriodEnd(), Date(25, Mar, 2021)); |
| 435 | |
| 436 | ext::shared_ptr<FixedRateCoupon> firstCpn2 = |
| 437 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg2.front()); |
| 438 | ext::shared_ptr<FixedRateCoupon> lastCpn2 = |
| 439 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg2.back()); |
| 440 | BOOST_REQUIRE(firstCpn2 != nullptr); |
| 441 | BOOST_REQUIRE(lastCpn2 != nullptr); |
| 442 | BOOST_CHECK_EQUAL(firstCpn2->referencePeriodStart(), Date(25, Mar, 2017)); |
| 443 | BOOST_CHECK_EQUAL(firstCpn2->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 444 | BOOST_CHECK_EQUAL(lastCpn2->referencePeriodStart(), Date(25, Sep, 2020)); |
| 445 | BOOST_CHECK_EQUAL(lastCpn2->referencePeriodEnd(), Date(25, Mar, 2021)); |
| 446 | |
| 447 | ext::shared_ptr<FixedRateCoupon> firstCpn3 = |
| 448 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg3.front()); |
| 449 | ext::shared_ptr<FixedRateCoupon> lastCpn3 = |
| 450 | ext::dynamic_pointer_cast<FixedRateCoupon>(r: leg3.back()); |
| 451 | BOOST_REQUIRE(firstCpn3 != nullptr); |
| 452 | BOOST_REQUIRE(lastCpn3 != nullptr); |
| 453 | BOOST_CHECK_EQUAL(firstCpn3->referencePeriodStart(), Date(15, Sep, 2017)); |
| 454 | BOOST_CHECK_EQUAL(firstCpn3->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 455 | BOOST_CHECK_EQUAL(lastCpn3->referencePeriodStart(), Date(25, Sep, 2020)); |
| 456 | BOOST_CHECK_EQUAL(lastCpn3->referencePeriodEnd(), Date(30, Sep, 2020)); |
| 457 | |
| 458 | // same check as above for a floating leg |
| 459 | ext::shared_ptr<IborIndex> iborIndex = |
| 460 | ext::make_shared<USDLibor>(args: 3 * Months); |
| 461 | Leg legf = IborLeg(schedule, iborIndex) |
| 462 | .withNotionals(notional: 100.0) |
| 463 | .withPaymentDayCounter(ActualActual(ActualActual::ISMA)); |
| 464 | Leg legf2 = IborLeg(schedule2, iborIndex) |
| 465 | .withNotionals(notional: 100.0) |
| 466 | .withPaymentDayCounter(ActualActual(ActualActual::ISMA)); |
| 467 | Leg legf3 = IborLeg(schedule3, iborIndex) |
| 468 | .withNotionals(notional: 100.0) |
| 469 | .withPaymentDayCounter(ActualActual(ActualActual::ISMA)); |
| 470 | |
| 471 | ext::shared_ptr<FloatingRateCoupon> firstCpnF = |
| 472 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf.front()); |
| 473 | ext::shared_ptr<FloatingRateCoupon> lastCpnF = |
| 474 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf.back()); |
| 475 | BOOST_REQUIRE(firstCpnF != nullptr); |
| 476 | BOOST_REQUIRE(lastCpnF != nullptr); |
| 477 | BOOST_CHECK_EQUAL(firstCpnF->referencePeriodStart(), Date(25, Mar, 2017)); |
| 478 | BOOST_CHECK_EQUAL(firstCpnF->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 479 | BOOST_CHECK_EQUAL(lastCpnF->referencePeriodStart(), Date(25, Sep, 2020)); |
| 480 | BOOST_CHECK_EQUAL(lastCpnF->referencePeriodEnd(), Date(25, Mar, 2021)); |
| 481 | |
| 482 | ext::shared_ptr<FloatingRateCoupon> firstCpnF2 = |
| 483 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf2.front()); |
| 484 | ext::shared_ptr<FloatingRateCoupon> lastCpnF2 = |
| 485 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf2.back()); |
| 486 | BOOST_REQUIRE(firstCpnF2 != nullptr); |
| 487 | BOOST_REQUIRE(lastCpnF2 != nullptr); |
| 488 | BOOST_CHECK_EQUAL(firstCpnF2->referencePeriodStart(), Date(25, Mar, 2017)); |
| 489 | BOOST_CHECK_EQUAL(firstCpnF2->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 490 | BOOST_CHECK_EQUAL(lastCpnF2->referencePeriodStart(), Date(25, Sep, 2020)); |
| 491 | BOOST_CHECK_EQUAL(lastCpnF2->referencePeriodEnd(), Date(25, Mar, 2021)); |
| 492 | |
| 493 | ext::shared_ptr<FloatingRateCoupon> firstCpnF3 = |
| 494 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf3.front()); |
| 495 | ext::shared_ptr<FloatingRateCoupon> lastCpnF3 = |
| 496 | ext::dynamic_pointer_cast<FloatingRateCoupon>(r: legf3.back()); |
| 497 | BOOST_REQUIRE(firstCpnF3 != nullptr); |
| 498 | BOOST_REQUIRE(lastCpnF3 != nullptr); |
| 499 | BOOST_CHECK_EQUAL(firstCpnF3->referencePeriodStart(), Date(15, Sep, 2017)); |
| 500 | BOOST_CHECK_EQUAL(firstCpnF3->referencePeriodEnd(), Date(25, Sep, 2017)); |
| 501 | BOOST_CHECK_EQUAL(lastCpnF3->referencePeriodStart(), Date(25, Sep, 2020)); |
| 502 | BOOST_CHECK_EQUAL(lastCpnF3->referencePeriodEnd(), Date(30, Sep, 2020)); |
| 503 | } |
| 504 | |
| 505 | void CashFlowsTest::testFixedIborCouponWithoutForecastCurve() { |
| 506 | BOOST_TEST_MESSAGE("Testing past ibor coupon without forecast curve..." ); |
| 507 | |
| 508 | Date today = Settings::instance().evaluationDate(); |
| 509 | |
| 510 | auto index = ext::make_shared<USDLibor>(args: 6*Months); |
| 511 | auto calendar = index->fixingCalendar(); |
| 512 | |
| 513 | Date fixingDate = calendar.advance(today, n: -2, unit: Months); |
| 514 | Rate pastFixing = 0.01; |
| 515 | index->addFixing(fixingDate, fixing: pastFixing); |
| 516 | |
| 517 | Date startDate = index->valueDate(fixingDate); |
| 518 | Date endDate = index->maturityDate(valueDate: fixingDate); |
| 519 | |
| 520 | IborCoupon coupon(endDate, 100.0, startDate, endDate, index->fixingDays(), index); |
| 521 | coupon.setPricer(ext::make_shared<BlackIborCouponPricer>()); |
| 522 | |
| 523 | BOOST_CHECK_NO_THROW(coupon.amount()); |
| 524 | |
| 525 | // the main check is the one above, but let's check for consistency too: |
| 526 | Real amount = coupon.amount(); |
| 527 | Real expected = pastFixing * coupon.nominal() * coupon.accrualPeriod(); |
| 528 | if (std::fabs(x: amount - expected) > 1e-8) { |
| 529 | BOOST_ERROR("amount mismatch:" |
| 530 | << "\n calculated: " << amount |
| 531 | << "\n expected: " << expected); |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | test_suite* CashFlowsTest::suite() { |
| 536 | auto* suite = BOOST_TEST_SUITE("Cash flows tests" ); |
| 537 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testSettings)); |
| 538 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testAccessViolation)); |
| 539 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testDefaultSettlementDate)); |
| 540 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testExCouponDates)); |
| 541 | |
| 542 | if (IborCoupon::Settings::instance().usingAtParCoupons()) |
| 543 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testNullFixingDays)); |
| 544 | |
| 545 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testIrregularFirstCouponReferenceDatesAtEndOfMonth)); |
| 546 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testIrregularLastCouponReferenceDatesAtEndOfMonth)); |
| 547 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testPartialScheduleLegConstruction)); |
| 548 | suite->add(QUANTLIB_TEST_CASE(&CashFlowsTest::testFixedIborCouponWithoutForecastCurve)); |
| 549 | |
| 550 | return suite; |
| 551 | } |
| 552 | |