| 1 | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| 2 | |
| 3 | /* |
| 4 | Copyright (C) 2018 Sebastian Schlenkrich |
| 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 "basismodels.hpp" |
| 21 | #include "utilities.hpp" |
| 22 | #include <ql/cashflows/iborcoupon.hpp> |
| 23 | #include <ql/compounding.hpp> |
| 24 | #include <ql/experimental/basismodels/swaptioncfs.hpp> |
| 25 | #include <ql/experimental/basismodels/tenoroptionletvts.hpp> |
| 26 | #include <ql/experimental/basismodels/tenorswaptionvts.hpp> |
| 27 | #include <ql/indexes/ibor/euribor.hpp> |
| 28 | #include <ql/instruments/swaption.hpp> |
| 29 | #include <ql/pricingengines/swap/discountingswapengine.hpp> |
| 30 | #include <ql/termstructures/volatility/optionlet/strippedoptionlet.hpp> |
| 31 | #include <ql/termstructures/volatility/optionlet/strippedoptionletadapter.hpp> |
| 32 | #include <ql/termstructures/volatility/swaption/swaptionvolmatrix.hpp> |
| 33 | #include <ql/termstructures/yield/zerocurve.hpp> |
| 34 | #include <ql/math/interpolations/cubicinterpolation.hpp> |
| 35 | #include <ql/quotes/simplequote.hpp> |
| 36 | #include <ql/time/calendars/target.hpp> |
| 37 | #include <ql/time/daycounters/thirty360.hpp> |
| 38 | |
| 39 | using namespace QuantLib; |
| 40 | using namespace boost::unit_test_framework; |
| 41 | |
| 42 | namespace { |
| 43 | |
| 44 | // auxiliary data |
| 45 | Period termsData[] = { |
| 46 | Period(0, Days), Period(1, Years), Period(2, Years), Period(3, Years), |
| 47 | Period(5, Years), Period(7, Years), Period(10, Years), Period(15, Years), |
| 48 | Period(20, Years), Period(61, Years) // avoid extrapolation issues with 30y caplets |
| 49 | }; |
| 50 | std::vector<Period> terms(termsData, termsData + 10); |
| 51 | |
| 52 | Real discRatesData[] = {-0.00147407, -0.001761684, -0.001736745, -0.00119244, 0.000896055, |
| 53 | 0.003537077, 0.007213824, 0.011391278, 0.013334611, 0.013982809}; |
| 54 | std::vector<Real> discRates(discRatesData, discRatesData + 10); |
| 55 | |
| 56 | Real proj3mRatesData[] = {-0.000483439, -0.000578569, -0.000383832, 0.000272656, 0.002478699, |
| 57 | 0.005100113, 0.008750643, 0.012788095, 0.014534052, 0.014942896}; |
| 58 | std::vector<Real> proj3mRates(proj3mRatesData, proj3mRatesData + 10); |
| 59 | |
| 60 | Real proj6mRatesData[] = {0.000233608, 0.000218862, 0.000504018, 0.001240556, 0.003554415, |
| 61 | 0.006153921, 0.009688264, 0.013521628, 0.015136391, 0.015377704}; |
| 62 | std::vector<Real> proj6mRates(proj6mRatesData, proj6mRatesData + 10); |
| 63 | |
| 64 | Handle<YieldTermStructure> getYTS(const std::vector<Period>& terms, |
| 65 | const std::vector<Real>& rates, |
| 66 | const Real spread = 0.0) { |
| 67 | Date today = Settings::instance().evaluationDate(); |
| 68 | std::vector<Date> dates; |
| 69 | dates.reserve(n: terms.size()); |
| 70 | for (auto term : terms) |
| 71 | dates.push_back(x: NullCalendar().advance(date: today, period: term, convention: Unadjusted)); |
| 72 | std::vector<Real> ratesPlusSpread(rates); |
| 73 | for (Real& k : ratesPlusSpread) |
| 74 | k += spread; |
| 75 | ext::shared_ptr<YieldTermStructure> ts = |
| 76 | ext::shared_ptr<YieldTermStructure>(new InterpolatedZeroCurve<Cubic>( |
| 77 | dates, ratesPlusSpread, Actual365Fixed(), NullCalendar())); |
| 78 | return RelinkableHandle<YieldTermStructure>(ts); |
| 79 | } |
| 80 | |
| 81 | Period capletTermsData[] = {Period(1, Years), Period(2, Years), Period(3, Years), |
| 82 | Period(5, Years), Period(7, Years), Period(10, Years), |
| 83 | Period(15, Years), Period(20, Years), Period(25, Years), |
| 84 | Period(30, Years)}; |
| 85 | std::vector<Period> capletTerms(capletTermsData, capletTermsData + 10); |
| 86 | |
| 87 | Real capletStrikesData[] = {-0.0050, 0.0000, 0.0050, 0.0100, 0.0150, 0.0200, 0.0300, 0.0500}; |
| 88 | std::vector<Real> capletStrikes(capletStrikesData, capletStrikesData + 8); |
| 89 | |
| 90 | |
| 91 | Handle<OptionletVolatilityStructure> getOptionletTS() { |
| 92 | Date today = Settings::instance().evaluationDate(); |
| 93 | std::vector<Date> dates; |
| 94 | dates.reserve(n: capletTerms.size()); |
| 95 | for (auto& capletTerm : capletTerms) |
| 96 | dates.push_back(x: TARGET().advance(date: today, period: capletTerm, convention: Following)); |
| 97 | // set up vol data manually |
| 98 | std::vector<std::vector<Real> > capletVols = |
| 99 | { |
| 100 | {0.003010094, 0.002628065, 0.00456118, 0.006731268, 0.008678572, 0.010570881, 0.014149552, 0.021000638}, |
| 101 | {0.004173715, 0.003727039, 0.004180263, 0.005726083, 0.006905876, 0.008263514, 0.010555395, 0.014976523}, |
| 102 | {0.005870143, 0.005334526, 0.005599775, 0.006633987, 0.007773317, 0.009036581, 0.011474391, 0.016277549}, |
| 103 | {0.007458597, 0.007207522, 0.007263995, 0.007308727, 0.007813586, 0.008274858, 0.009743988, 0.012555171}, |
| 104 | {0.007711531, 0.007608826, 0.007572816, 0.007684107, 0.007971932, 0.008283118, 0.009268828, 0.011574083}, |
| 105 | {0.007619605, 0.007639059, 0.007719825, 0.007823373, 0.00800813, 0.008113384, 0.008616374, 0.009785436}, |
| 106 | {0.007312199, 0.007352993, 0.007369116, 0.007468333, 0.007515657, 0.00767695, 0.008020447, 0.009072769}, |
| 107 | {0.006905851, 0.006966315, 0.007056413, 0.007116494, 0.007259661, 0.00733308, 0.007667563, 0.008419696}, |
| 108 | {0.006529553, 0.006630731, 0.006749022, 0.006858027, 0.007001959, 0.007139097, 0.007390404, 0.008036255}, |
| 109 | {0.006225482, 0.006404012, 0.00651594, 0.006642273, 0.006640887, 0.006885713, 0.007093024, 0.00767373} |
| 110 | }; |
| 111 | // create quotes |
| 112 | std::vector<std::vector<Handle<Quote> > > capletVolQuotes; |
| 113 | for (auto& capletVol : capletVols) { |
| 114 | std::vector<Handle<Quote> > row; |
| 115 | row.reserve(n: capletVol.size()); |
| 116 | for (Real j : capletVol) |
| 117 | row.push_back(x: RelinkableHandle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(j)))); |
| 118 | capletVolQuotes.push_back(x: row); |
| 119 | } |
| 120 | Handle<YieldTermStructure> curve3m = getYTS(terms, rates: proj3mRates); |
| 121 | ext::shared_ptr<IborIndex> index(new Euribor3M(curve3m)); |
| 122 | ext::shared_ptr<StrippedOptionletBase> tmp1( |
| 123 | new StrippedOptionlet(2, TARGET(), Following, index, dates, capletStrikes, |
| 124 | capletVolQuotes, Actual365Fixed(), Normal, 0.0)); |
| 125 | ext::shared_ptr<StrippedOptionletAdapter> tmp2(new StrippedOptionletAdapter(tmp1)); |
| 126 | return RelinkableHandle<OptionletVolatilityStructure>(tmp2); |
| 127 | } |
| 128 | |
| 129 | Period swaptionVTSTermsData[] = { |
| 130 | Period(1, Years), Period(5, Years), Period(10, Years), Period(20, Years), Period(30, Years), |
| 131 | }; |
| 132 | std::vector<Period> swaptionVTSTerms(swaptionVTSTermsData, swaptionVTSTermsData + 5); |
| 133 | |
| 134 | Handle<SwaptionVolatilityStructure> getSwaptionVTS() { |
| 135 | std::vector<std::vector<Real> > swaptionVols = |
| 136 | { |
| 137 | {0.002616, 0.00468, 0.0056, 0.005852, 0.005823}, |
| 138 | {0.006213, 0.00643, 0.006622, 0.006124, 0.005958}, |
| 139 | {0.006658, 0.006723, 0.006602, 0.005802, 0.005464}, |
| 140 | {0.005728, 0.005814, 0.005663, 0.004689, 0.004276}, |
| 141 | {0.005041, 0.005059, 0.004746, 0.003927, 0.003608} |
| 142 | }; |
| 143 | std::vector<std::vector<Handle<Quote> > > swaptionVolQuotes; |
| 144 | for (auto& swaptionVol : swaptionVols) { |
| 145 | std::vector<Handle<Quote> > row; |
| 146 | row.reserve(n: swaptionVol.size()); |
| 147 | for (Real j : swaptionVol) |
| 148 | row.push_back(x: RelinkableHandle<Quote>(ext::shared_ptr<Quote>(new SimpleQuote(j)))); |
| 149 | swaptionVolQuotes.push_back(x: row); |
| 150 | } |
| 151 | ext::shared_ptr<SwaptionVolatilityStructure> tmp( |
| 152 | new SwaptionVolatilityMatrix(TARGET(), Following, swaptionVTSTerms, swaptionVTSTerms, |
| 153 | swaptionVolQuotes, Actual365Fixed(), true, Normal)); |
| 154 | return RelinkableHandle<SwaptionVolatilityStructure>(tmp); |
| 155 | } |
| 156 | |
| 157 | void testSwaptioncfs(bool contTenorSpread) { |
| 158 | bool usingAtParCoupons = IborCoupon::Settings::instance().usingAtParCoupons(); |
| 159 | // market data and floating rate index |
| 160 | Handle<YieldTermStructure> discYTS = getYTS(terms, rates: discRates); |
| 161 | Handle<YieldTermStructure> proj6mYTS = getYTS(terms, rates: proj6mRates); |
| 162 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M(proj6mYTS)); |
| 163 | // Vanilla swap details |
| 164 | Date today = Settings::instance().evaluationDate(); |
| 165 | Date swapStart = TARGET().advance(date: today, period: Period(5, Years), convention: Following); |
| 166 | Date swapEnd = TARGET().advance(date: swapStart, period: Period(10, Years), convention: Following); |
| 167 | Date exerciseDate = TARGET().advance(date: swapStart, period: Period(-2, Days), convention: Preceding); |
| 168 | Schedule fixedSchedule(swapStart, swapEnd, Period(1, Years), TARGET(), ModifiedFollowing, |
| 169 | ModifiedFollowing, DateGeneration::Backward, false); |
| 170 | Schedule floatSchedule(swapStart, swapEnd, Period(6, Months), TARGET(), ModifiedFollowing, |
| 171 | ModifiedFollowing, DateGeneration::Backward, false); |
| 172 | ext::shared_ptr<VanillaSwap> swap( |
| 173 | new VanillaSwap(Swap::Payer, 10000.0, fixedSchedule, 0.03, Thirty360(Thirty360::BondBasis), |
| 174 | floatSchedule, euribor6m, 0.0, euribor6m->dayCounter())); |
| 175 | swap->setPricingEngine(ext::shared_ptr<PricingEngine>(new DiscountingSwapEngine(discYTS))); |
| 176 | // European exercise and swaption |
| 177 | ext::shared_ptr<Exercise> europeanExercise(new EuropeanExercise(exerciseDate)); |
| 178 | ext::shared_ptr<Swaption> swaption( |
| 179 | new Swaption(swap, europeanExercise, Settlement::Physical)); |
| 180 | // calculate basis model swaption cash flows, discount and conmpare with swap |
| 181 | SwaptionCashFlows cashFlows(swaption, discYTS, contTenorSpread); |
| 182 | // model time is always Act365Fixed |
| 183 | Time exerciseTime = Actual365Fixed().yearFraction(d1: discYTS->referenceDate(), |
| 184 | d2: swaption->exercise()->dates()[0]); |
| 185 | if (exerciseTime != cashFlows.exerciseTimes()[0]) |
| 186 | BOOST_ERROR( |
| 187 | "Swaption cash flow exercise time does not coincide with manual calculation" ); |
| 188 | // there might be rounding errors |
| 189 | Real tol = 1.0e-8; |
| 190 | // (discounted) fixed leg coupons must match swap fixed leg NPV |
| 191 | Real fixedLeg = 0.0; |
| 192 | for (Size k = 0; k < cashFlows.fixedTimes().size(); ++k) |
| 193 | fixedLeg += cashFlows.fixedWeights()[k] * discYTS->discount(t: cashFlows.fixedTimes()[k]); |
| 194 | if (fabs(x: fixedLeg - (-swap->fixedLegNPV())) > tol) // note, '-1' because payer swap |
| 195 | BOOST_ERROR("Swaption cash flow fixed leg NPV does not match Vanillaswap fixed leg NPV" |
| 196 | << "SwaptionCashFlows: " << fixedLeg << "\n" |
| 197 | << "swap->fixedLegNPV: " << swap->fixedLegNPV() << "\n" |
| 198 | << "Variance: " << swap->fixedLegNPV() - fixedLeg << "\n" ); |
| 199 | // (discounted) floating leg coupons must match swap floating leg NPV |
| 200 | Real floatLeg = 0.0; |
| 201 | for (Size k = 0; k < cashFlows.floatTimes().size(); ++k) |
| 202 | floatLeg += cashFlows.floatWeights()[k] * discYTS->discount(t: cashFlows.floatTimes()[k]); |
| 203 | if (fabs(x: floatLeg - swap->floatingLegNPV()) > tol) |
| 204 | BOOST_ERROR( |
| 205 | "Swaption cash flow floating leg NPV does not match Vanillaswap floating leg NPV.\n" |
| 206 | << "SwaptionCashFlows: " << floatLeg << "\n" |
| 207 | << "swap->floatingLegNPV: " << swap->floatingLegNPV() << "\n" |
| 208 | << "Variance: " << swap->floatingLegNPV() - floatLeg << "\n" ); |
| 209 | // There should not be spread coupons in a single-curve setting. |
| 210 | // However, if indexed coupons are used the floating leg is not at par, |
| 211 | // so we need to relax the tolerance to a level at which it will only |
| 212 | // catch large errors. |
| 213 | Real tol2 = usingAtParCoupons ? tol : 0.02; |
| 214 | |
| 215 | SwaptionCashFlows singleCurveCashFlows(swaption, proj6mYTS, contTenorSpread); |
| 216 | for (Size k = 1; k < singleCurveCashFlows.floatWeights().size() - 1; ++k) { |
| 217 | if (fabs(x: singleCurveCashFlows.floatWeights()[k]) > tol2) |
| 218 | BOOST_ERROR("Swaption cash flow floating leg spread does not vanish in " |
| 219 | "single-curve setting.\n" |
| 220 | << "Cash flow index k: " << k << ", floatWeights: " |
| 221 | << singleCurveCashFlows.floatWeights()[k] << "\n" ); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | } |
| 226 | |
| 227 | |
| 228 | void BasismodelsTest::testSwaptioncfsContCompSpread() { |
| 229 | BOOST_TEST_MESSAGE( |
| 230 | "Testing deterministic tenor basis model with continuous compounded spreads..." ); |
| 231 | testSwaptioncfs(contTenorSpread: true); |
| 232 | } |
| 233 | |
| 234 | void BasismodelsTest::testSwaptioncfsSimpleCompSpread() { |
| 235 | BOOST_TEST_MESSAGE("Testing deterministic tenor basis model with simple compounded spreads..." ); |
| 236 | testSwaptioncfs(contTenorSpread: false); |
| 237 | } |
| 238 | |
| 239 | void BasismodelsTest::testTenoroptionletvts() { |
| 240 | BOOST_TEST_MESSAGE("Testing volatility transformation for caplets/floorlets..." ); |
| 241 | // market data and floating rate index |
| 242 | Real spread = 0.01; |
| 243 | Handle<YieldTermStructure> discYTS = getYTS(terms, rates: discRates); |
| 244 | Handle<YieldTermStructure> proj3mYTS = getYTS(terms, rates: proj3mRates); |
| 245 | Handle<YieldTermStructure> proj6mYTS = getYTS(terms, rates: proj3mRates, spread); |
| 246 | ext::shared_ptr<IborIndex> euribor3m(new Euribor6M(proj3mYTS)); |
| 247 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M(proj6mYTS)); |
| 248 | // 3m optionlet VTS |
| 249 | Handle<OptionletVolatilityStructure> optionletVTS3m = getOptionletTS(); |
| 250 | { |
| 251 | // we need a correlation structure |
| 252 | Real corrTimesRaw[] = {0.0, 50.0}; |
| 253 | Real rhoInfDataRaw[] = {0.3, 0.3}; |
| 254 | Real betaDataRaw[] = {0.9, 0.9}; |
| 255 | std::vector<Real> corrTimes(corrTimesRaw, corrTimesRaw + 2); |
| 256 | std::vector<Real> rhoInfData(rhoInfDataRaw, rhoInfDataRaw + 2); |
| 257 | std::vector<Real> betaData(betaDataRaw, betaDataRaw + 2); |
| 258 | ext::shared_ptr<Interpolation> rho( |
| 259 | new LinearInterpolation(corrTimes.begin(), corrTimes.end(), rhoInfData.begin())); |
| 260 | ext::shared_ptr<Interpolation> beta( |
| 261 | new LinearInterpolation(corrTimes.begin(), corrTimes.end(), betaData.begin())); |
| 262 | ext::shared_ptr<TenorOptionletVTS::CorrelationStructure> corr( |
| 263 | new TenorOptionletVTS::TwoParameterCorrelation(rho, beta)); |
| 264 | // now we can set up the new volTS and calculate volatilities |
| 265 | ext::shared_ptr<OptionletVolatilityStructure> optionletVTS6m( |
| 266 | new TenorOptionletVTS(optionletVTS3m, euribor3m, euribor6m, corr)); |
| 267 | for (auto& capletTerm : capletTerms) { |
| 268 | for (Real& capletStrike : capletStrikes) { |
| 269 | Real vol3m = optionletVTS3m->volatility(optionTenor: capletTerm, strike: capletStrike, extrapolate: true); |
| 270 | Real vol6m = optionletVTS6m->volatility(optionTenor: capletTerm, strike: capletStrike, extrapolate: true); |
| 271 | Real vol6mShifted = |
| 272 | optionletVTS6m->volatility(optionTenor: capletTerm, strike: capletStrike + spread, extrapolate: true); |
| 273 | // De-correlation yields that larger tenor shifted vols are smaller then shorter |
| 274 | // tenor vols |
| 275 | if (vol6mShifted - vol3m > |
| 276 | 0.0001) // we leave 1bp tolerance due to simplified spread calculation |
| 277 | BOOST_ERROR("Shifted 6m vol significantly larger then 3m vol at\n" |
| 278 | << "expiry term: " << capletTerm << ", strike: " << capletStrike |
| 279 | << "\n" |
| 280 | << "vol3m: " << vol3m << ", vol6m: " << vol6m |
| 281 | << ", vol6mShifted: " << vol6mShifted << "\n" ); |
| 282 | } |
| 283 | } |
| 284 | } |
| 285 | { |
| 286 | // we need a correlation structure |
| 287 | Real corrTimesRaw[] = {0.0, 50.0}; |
| 288 | Real rhoInfDataRaw[] = {0.0, 0.0}; |
| 289 | Real betaDataRaw[] = {0.0, 0.0}; |
| 290 | std::vector<Real> corrTimes(corrTimesRaw, corrTimesRaw + 2); |
| 291 | std::vector<Real> rhoInfData(rhoInfDataRaw, rhoInfDataRaw + 2); |
| 292 | std::vector<Real> betaData(betaDataRaw, betaDataRaw + 2); |
| 293 | ext::shared_ptr<Interpolation> rho( |
| 294 | new LinearInterpolation(corrTimes.begin(), corrTimes.end(), rhoInfData.begin())); |
| 295 | ext::shared_ptr<Interpolation> beta( |
| 296 | new LinearInterpolation(corrTimes.begin(), corrTimes.end(), betaData.begin())); |
| 297 | ext::shared_ptr<TenorOptionletVTS::CorrelationStructure> corr( |
| 298 | new TenorOptionletVTS::TwoParameterCorrelation(rho, beta)); |
| 299 | // now we can set up the new volTS and calculate volatilities |
| 300 | ext::shared_ptr<OptionletVolatilityStructure> optionletVTS6m( |
| 301 | new TenorOptionletVTS(optionletVTS3m, euribor3m, euribor6m, corr)); |
| 302 | for (Size i = 0; i < capletTerms.size(); ++i) { |
| 303 | for (Real& capletStrike : capletStrikes) { |
| 304 | Real vol3m = optionletVTS3m->volatility(optionTenor: capletTerms[i], strike: capletStrike, extrapolate: true); |
| 305 | Real vol6m = optionletVTS6m->volatility(optionTenor: capletTerms[i], strike: capletStrike, extrapolate: true); |
| 306 | Real vol6mShifted = |
| 307 | optionletVTS6m->volatility(optionTenor: capletTerms[i], strike: capletStrike + spread, extrapolate: true); |
| 308 | // for perfect correlation shifted 6m vols should coincide with 3m vols |
| 309 | Real tol = |
| 310 | (i < 3) ? (0.001) : |
| 311 | (0.0001); // 10bp tol for smaller tenors and 1bp tol for larger tenors |
| 312 | if (fabs(x: vol6mShifted - vol3m) > tol) |
| 313 | BOOST_ERROR("Shifted 6m vol does not match 3m vol for perfect correlation at\n" |
| 314 | << "expiry term: " << capletTerms[i] << ", strike: " << capletStrike |
| 315 | << "\n" |
| 316 | << "vol3m: " << vol3m << ", vol6m: " << vol6m |
| 317 | << ", vol6mShifted: " << vol6mShifted << "\n" ); |
| 318 | } |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | void BasismodelsTest::testTenorswaptionvts() { |
| 324 | BOOST_TEST_MESSAGE("Testing volatility transformation for swaptions..." ); |
| 325 | // market data and floating rate index |
| 326 | Real spread = 0.01; |
| 327 | Handle<YieldTermStructure> discYTS = getYTS(terms, rates: discRates); |
| 328 | Handle<YieldTermStructure> proj3mYTS = getYTS(terms, rates: proj3mRates); |
| 329 | Handle<YieldTermStructure> proj6mYTS = getYTS(terms, rates: proj3mRates, spread); |
| 330 | ext::shared_ptr<IborIndex> euribor3m(new Euribor6M(proj3mYTS)); |
| 331 | ext::shared_ptr<IborIndex> euribor6m(new Euribor6M(proj6mYTS)); |
| 332 | // Euribor6m ATM vols |
| 333 | Handle<SwaptionVolatilityStructure> euribor6mSwVTS = getSwaptionVTS(); |
| 334 | { |
| 335 | ext::shared_ptr<TenorSwaptionVTS> euribor3mSwVTS( |
| 336 | new TenorSwaptionVTS(euribor6mSwVTS, discYTS, euribor6m, euribor3m, Period(1, Years), |
| 337 | Period(1, Years), Thirty360(Thirty360::BondBasis), Thirty360(Thirty360::BondBasis))); |
| 338 | // 6m vols should be slightly larger then 3m vols due to basis |
| 339 | for (Size i = 0; i < swaptionVTSTerms.size(); ++i) { |
| 340 | for (Size j = 0; j < swaptionVTSTerms.size(); ++j) { |
| 341 | Real vol6m = euribor6mSwVTS->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 342 | strike: 0.01, extrapolate: true); |
| 343 | Real vol3m = euribor3mSwVTS->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 344 | strike: 0.01, extrapolate: true); |
| 345 | if (vol3m > vol6m) |
| 346 | BOOST_ERROR("Euribor 6m must be larger than 3m vol at\n" |
| 347 | << "expiry term: " << swaptionVTSTerms[i] |
| 348 | << ", swap term: " << swaptionVTSTerms[j] << "\n" |
| 349 | << "vol3m: " << vol3m << ", vol6m: " << vol6m << "\n" ); |
| 350 | } |
| 351 | } |
| 352 | } |
| 353 | { |
| 354 | ext::shared_ptr<TenorSwaptionVTS> euribor6mSwVTS2( |
| 355 | new TenorSwaptionVTS(euribor6mSwVTS, discYTS, euribor6m, euribor6m, Period(1, Years), |
| 356 | Period(1, Years), Thirty360(Thirty360::BondBasis), Thirty360(Thirty360::BondBasis))); |
| 357 | // 6m vols to 6m vols should yield initiial vols |
| 358 | for (Size i = 0; i < swaptionVTSTerms.size(); ++i) { |
| 359 | for (Size j = 0; j < swaptionVTSTerms.size(); ++j) { |
| 360 | Real vol6m = euribor6mSwVTS->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 361 | strike: 0.01, extrapolate: true); |
| 362 | Real vol6m2 = euribor6mSwVTS2->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 363 | strike: 0.01, extrapolate: true); |
| 364 | Real tol = 1.0e-8; |
| 365 | if (fabs(x: vol6m2 - vol6m) > tol) |
| 366 | BOOST_ERROR("Euribor 6m to 6m vols should not change at\n" |
| 367 | << "expiry term: " << swaptionVTSTerms[i] |
| 368 | << ", swap term: " << swaptionVTSTerms[j] << "\n" |
| 369 | << "vol6m: " << vol6m << ", vol6m2: " << vol6m2 |
| 370 | << ", variance: " << (vol6m2 - vol6m) << "\n" ); |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | { |
| 375 | ext::shared_ptr<TenorSwaptionVTS> euribor3mSwVTS( |
| 376 | new TenorSwaptionVTS(euribor6mSwVTS, discYTS, euribor6m, euribor3m, Period(1, Years), |
| 377 | Period(1, Years), Thirty360(Thirty360::BondBasis), Thirty360(Thirty360::BondBasis))); |
| 378 | ext::shared_ptr<TenorSwaptionVTS> euribor6mSwVTS2(new TenorSwaptionVTS( |
| 379 | RelinkableHandle<SwaptionVolatilityStructure>(euribor3mSwVTS), discYTS, euribor3m, |
| 380 | euribor6m, Period(1, Years), Period(1, Years), Thirty360(Thirty360::BondBasis), Thirty360(Thirty360::BondBasis))); |
| 381 | // 6m vols to 6m vols should yield initiial vols |
| 382 | for (Size i = 0; i < swaptionVTSTerms.size(); ++i) { |
| 383 | for (Size j = 0; j < swaptionVTSTerms.size(); ++j) { |
| 384 | Real vol6m = euribor6mSwVTS->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 385 | strike: 0.01, extrapolate: true); |
| 386 | Real vol6m2 = euribor6mSwVTS2->volatility(optionTenor: swaptionVTSTerms[i], swapTenor: swaptionVTSTerms[j], |
| 387 | strike: 0.01, extrapolate: true); |
| 388 | Real tol = 1.0e-8; |
| 389 | if (fabs(x: vol6m2 - vol6m) > tol) |
| 390 | BOOST_ERROR("Euribor 6m to 3m to 6m vols should not change at\n" |
| 391 | << "expiry term: " << swaptionVTSTerms[i] |
| 392 | << ", swap term: " << swaptionVTSTerms[j] << "\n" |
| 393 | << "vol6m: " << vol6m << ", vol6m2: " << vol6m2 |
| 394 | << ", variance: " << (vol6m2 - vol6m) << "\n" ); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | |
| 401 | test_suite* BasismodelsTest::suite() { |
| 402 | auto* suite = BOOST_TEST_SUITE("Basismodels tests" ); |
| 403 | suite->add(QUANTLIB_TEST_CASE(&BasismodelsTest::testSwaptioncfsContCompSpread)); |
| 404 | suite->add(QUANTLIB_TEST_CASE(&BasismodelsTest::testSwaptioncfsSimpleCompSpread)); |
| 405 | suite->add(QUANTLIB_TEST_CASE(&BasismodelsTest::testTenoroptionletvts)); |
| 406 | suite->add(QUANTLIB_TEST_CASE(&BasismodelsTest::testTenorswaptionvts)); |
| 407 | return suite; |
| 408 | } |
| 409 | |