/** Test {@link BlackVolatilityExpiryTenorSwaptionProvider}. */ @Test public class BlackVolatilityExpiryTenorSwaptionProviderTest { private static final Interpolator1D LINEAR_FLAT = CombinedInterpolatorExtrapolator.of( CurveInterpolators.LINEAR.getName(), CurveExtrapolators.FLAT.getName(), CurveExtrapolators.FLAT.getName()); private static final GridInterpolator2D INTERPOLATOR_2D = new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT); private static final DoubleArray TIME = DoubleArray.of(0.25, 0.5, 1.0, 0.25, 0.5, 1.0, 0.25, 0.5, 1.0, 0.25, 0.5, 1.0); private static final DoubleArray TENOR = DoubleArray.of(3.0, 3.0, 3.0, 5.0, 5.0, 5.0, 7.0, 7.0, 7.0, 10.0, 10.0, 10.0); private static final DoubleArray VOL = DoubleArray.of(0.14, 0.12, 0.1, 0.14, 0.13, 0.12, 0.13, 0.12, 0.11, 0.12, 0.11, 0.1); private static final SurfaceMetadata METADATA_WITH_PARAM; private static final SurfaceMetadata METADATA; static { List<SwaptionSurfaceExpiryTenorNodeMetadata> list = new ArrayList<SwaptionSurfaceExpiryTenorNodeMetadata>(); int nData = TIME.size(); for (int i = 0; i < nData; ++i) { SwaptionSurfaceExpiryTenorNodeMetadata parameterMetadata = SwaptionSurfaceExpiryTenorNodeMetadata.of(TIME.get(i), TENOR.get(i)); list.add(parameterMetadata); } METADATA_WITH_PARAM = DefaultSurfaceMetadata.builder() .dayCount(ACT_365F) .parameterMetadata(list) .surfaceName(SurfaceName.of("GOVT1-SWAPTION-VOL")) .xValueType(ValueType.YEAR_FRACTION) .yValueType(ValueType.YEAR_FRACTION) .build(); METADATA = DefaultSurfaceMetadata.builder() .dayCount(ACT_365F) .surfaceName(SurfaceName.of("GOVT1-SWAPTION-VOL")) .xValueType(ValueType.YEAR_FRACTION) .yValueType(ValueType.YEAR_FRACTION) .build(); } private static final InterpolatedNodalSurface SURFACE_WITH_PARAM = InterpolatedNodalSurface.of(METADATA_WITH_PARAM, TIME, TENOR, VOL, INTERPOLATOR_2D); private static final InterpolatedNodalSurface SURFACE = InterpolatedNodalSurface.of(METADATA, TIME, TENOR, VOL, INTERPOLATOR_2D); private static final FixedIborSwapConvention CONVENTION = FixedIborSwapConventions.GBP_FIXED_1Y_LIBOR_3M; private static final LocalDate VALUATION_DATE = date(2015, 2, 17); private static final LocalTime VALUATION_TIME = LocalTime.of(13, 45); private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London"); private static final ZonedDateTime VALUATION_DATE_TIME = VALUATION_DATE.atTime(VALUATION_TIME).atZone(LONDON_ZONE); private static final BlackVolatilityExpiryTenorSwaptionProvider PROVIDER_WITH_PARAM = BlackVolatilityExpiryTenorSwaptionProvider.of( SURFACE_WITH_PARAM, CONVENTION, ACT_365F, VALUATION_DATE, VALUATION_TIME, LONDON_ZONE); private static final BlackVolatilityExpiryTenorSwaptionProvider PROVIDER = BlackVolatilityExpiryTenorSwaptionProvider.of( SURFACE, CONVENTION, ACT_365F, VALUATION_DATE, VALUATION_TIME, LONDON_ZONE); private static final ZonedDateTime[] TEST_OPTION_EXPIRY = new ZonedDateTime[] { dateUtc(2015, 2, 17), dateUtc(2015, 5, 17), dateUtc(2015, 6, 17), dateUtc(2017, 2, 17) }; private static final int NB_TEST = TEST_OPTION_EXPIRY.length; private static final double[] TEST_TENOR = new double[] {2.0, 6.0, 7.0, 15.0}; private static final double[] TEST_SENSITIVITY = new double[] {1.0, 1.0, 1.0, 1.0}; private static final double TEST_FORWARD = 0.025; // not used internally private static final double TEST_STRIKE = 0.03; // not used internally private static final double TOLERANCE_VOL = 1.0E-10; // ------------------------------------------------------------------------- public void test_valuationDate() { assertEquals(PROVIDER_WITH_PARAM.getValuationDateTime(), VALUATION_DATE_TIME); } public void test_swapConvention() { assertEquals(PROVIDER_WITH_PARAM.getConvention(), CONVENTION); } public void test_tenor() { double test1 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, VALUATION_DATE); assertEquals(test1, 0d); double test2 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, date(2018, 2, 28)); assertEquals(test2, 3d); double test3 = PROVIDER_WITH_PARAM.tenor(VALUATION_DATE, date(2018, 2, 10)); assertEquals(test3, 3d); } public void test_relativeTime() { double test1 = PROVIDER_WITH_PARAM.relativeTime(VALUATION_DATE_TIME); assertEquals(test1, 0d); double test2 = PROVIDER_WITH_PARAM.relativeTime(date(2018, 2, 17).atStartOfDay(LONDON_ZONE)); double test3 = PROVIDER_WITH_PARAM.relativeTime(date(2012, 2, 17).atStartOfDay(LONDON_ZONE)); assertEquals(test2, -test3); // consistency checked } public void test_volatility() { for (int i = 0; i < NB_TEST; i++) { double expiryTime = PROVIDER_WITH_PARAM.relativeTime(TEST_OPTION_EXPIRY[i]); double volExpected = SURFACE_WITH_PARAM.zValue(expiryTime, TEST_TENOR[i]); double volComputed = PROVIDER_WITH_PARAM.getVolatility( TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD); assertEquals(volComputed, volExpected, TOLERANCE_VOL); } } public void test_volatility_sensitivity() { double eps = 1.0e-6; int nData = TIME.size(); for (int i = 0; i < NB_TEST; i++) { SwaptionSensitivity point = SwaptionSensitivity.of( CONVENTION, TEST_OPTION_EXPIRY[i], TENOR.get(i), TEST_STRIKE, TEST_FORWARD, GBP, TEST_SENSITIVITY[i]); SurfaceCurrencyParameterSensitivity sensi = PROVIDER_WITH_PARAM.surfaceCurrencyParameterSensitivity(point); Map<DoublesPair, Double> map = new HashMap<DoublesPair, Double>(); for (int j = 0; j < nData; ++j) { DoubleArray volDataUp = VOL.subArray(0, nData).with(j, VOL.get(j) + eps); DoubleArray volDataDw = VOL.subArray(0, nData).with(j, VOL.get(j) - eps); InterpolatedNodalSurface paramUp = InterpolatedNodalSurface.of( METADATA_WITH_PARAM, TIME, TENOR, volDataUp, INTERPOLATOR_2D); InterpolatedNodalSurface paramDw = InterpolatedNodalSurface.of( METADATA_WITH_PARAM, TIME, TENOR, volDataDw, INTERPOLATOR_2D); BlackVolatilityExpiryTenorSwaptionProvider provUp = BlackVolatilityExpiryTenorSwaptionProvider.of( paramUp, CONVENTION, ACT_365F, VALUATION_DATE_TIME); BlackVolatilityExpiryTenorSwaptionProvider provDw = BlackVolatilityExpiryTenorSwaptionProvider.of( paramDw, CONVENTION, ACT_365F, VALUATION_DATE_TIME); double volUp = provUp.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD); double volDw = provDw.getVolatility(TEST_OPTION_EXPIRY[i], TEST_TENOR[i], TEST_STRIKE, TEST_FORWARD); double fd = 0.5 * (volUp - volDw) / eps; map.put(DoublesPair.of(TIME.get(j), TENOR.get(j)), fd); } SurfaceCurrencyParameterSensitivity sensiFromNoMetadata = PROVIDER.surfaceCurrencyParameterSensitivity(point); List<SurfaceParameterMetadata> list = sensi.getMetadata().getParameterMetadata().get(); DoubleArray computed = sensi.getSensitivity(); assertEquals(computed.size(), nData); for (int j = 0; j < list.size(); ++j) { SwaptionSurfaceExpiryTenorNodeMetadata metadata = (SwaptionSurfaceExpiryTenorNodeMetadata) list.get(i); double expected = map.get(DoublesPair.of(metadata.getYearFraction(), metadata.getTenor())); assertEquals(computed.get(i), expected, eps); assertTrue( sensiFromNoMetadata.getMetadata().getParameterMetadata().get().contains(metadata)); } } } // ------------------------------------------------------------------------- public void coverage() { BlackVolatilityExpiryTenorSwaptionProvider test1 = BlackVolatilityExpiryTenorSwaptionProvider.of( SURFACE_WITH_PARAM, CONVENTION, ACT_365F, VALUATION_DATE_TIME); coverImmutableBean(test1); BlackVolatilityExpiryTenorSwaptionProvider test2 = BlackVolatilityExpiryTenorSwaptionProvider.of(SURFACE, CONVENTION, ACT_360, VALUATION_DATE); coverBeanEquals(test1, test2); } }
/** Black volatility data sets for testing. */ public class SwaptionNormalVolatilityDataSets { private static final double BP1 = 1.0E-4; private static final Interpolator1D LINEAR_FLAT = CombinedInterpolatorExtrapolator.of( CurveInterpolators.LINEAR.getName(), CurveExtrapolators.FLAT.getName(), CurveExtrapolators.FLAT.getName()); private static final GridInterpolator2D INTERPOLATOR_2D = new GridInterpolator2D(LINEAR_FLAT, LINEAR_FLAT); // ===== Standard figures for testing ===== private static final DoubleArray TIMES = DoubleArray.of( 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0, 0.50, 1.00, 5.00, 10.0); private static final DoubleArray TENOR = DoubleArray.of( 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0, 10.0, 30.0, 30.0, 30.0, 30.0); private static final DoubleArray NORMAL_VOL = DoubleArray.of( 0.010, 0.011, 0.012, 0.013, 0.011, 0.012, 0.013, 0.014, 0.012, 0.013, 0.014, 0.015, 0.013, 0.014, 0.015, 0.016, 0.014, 0.015, 0.016, 0.017); private static final SurfaceMetadata METADATA = DefaultSurfaceMetadata.builder() .xValueType(ValueType.YEAR_FRACTION) .yValueType(ValueType.YEAR_FRACTION) .zValueType(ValueType.VOLATILITY) .surfaceName(SurfaceName.of("Normal Vol")) .build(); private static final NodalSurface SURFACE_STD = InterpolatedNodalSurface.of(METADATA, TIMES, TENOR, NORMAL_VOL, INTERPOLATOR_2D); private static final LocalDate VALUATION_DATE_STD = RatesProviderDataSets.VAL_DATE_2014_01_22; private static final LocalTime VALUATION_TIME_STD = LocalTime.of(13, 45); private static final ZoneId VALUATION_ZONE_STD = ZoneId.of("Europe/London"); private static final BusinessDayAdjustment MOD_FOL_US = BusinessDayAdjustment.of(MODIFIED_FOLLOWING, USNY); private static final FixedRateSwapLegConvention USD_FIXED_1Y_30U360 = FixedRateSwapLegConvention.of(USD, THIRTY_U_360, Frequency.P6M, MOD_FOL_US); private static final IborRateSwapLegConvention USD_IBOR_LIBOR3M = IborRateSwapLegConvention.of(USD_LIBOR_3M); public static final FixedIborSwapConvention USD_1Y_LIBOR3M = ImmutableFixedIborSwapConvention.of("USD-Swap", USD_FIXED_1Y_30U360, USD_IBOR_LIBOR3M); public static final NormalVolatilityExpiryTenorSwaptionProvider NORMAL_VOL_SWAPTION_PROVIDER_USD_STD = NormalVolatilityExpiryTenorSwaptionProvider.of( SURFACE_STD, USD_1Y_LIBOR3M, DayCounts.ACT_365F, VALUATION_DATE_STD, VALUATION_TIME_STD, VALUATION_ZONE_STD); /** * Returns the swaption normal volatility surface shifted by a given amount. The shift is * parallel. * * @param shift the shift * @return the swaption normal volatility surface */ public static NormalVolatilityExpiryTenorSwaptionProvider normalVolSwaptionProviderUsdStsShifted( double shift) { DoubleArray volShifted = NORMAL_VOL.map(v -> v + shift); return NormalVolatilityExpiryTenorSwaptionProvider.of( InterpolatedNodalSurface.of(METADATA, TIMES, TENOR, volShifted, INTERPOLATOR_2D), USD_1Y_LIBOR3M, DayCounts.ACT_365F, VALUATION_DATE_STD, VALUATION_TIME_STD, VALUATION_ZONE_STD); } public static NormalVolatilityExpiryTenorSwaptionProvider normalVolSwaptionProviderUsdStd( LocalDate valuationDate) { return NormalVolatilityExpiryTenorSwaptionProvider.of( SURFACE_STD, USD_1Y_LIBOR3M, DayCounts.ACT_365F, valuationDate, VALUATION_TIME_STD, VALUATION_ZONE_STD); } // ===== Flat volatilities for testing ===== private static final DoubleArray TIMES_FLAT = DoubleArray.of(0.0, 100.0, 0.0, 100.0); private static final DoubleArray TENOR_FLAT = DoubleArray.of(0.0, 0.0, 30.0, 30.0); private static final DoubleArray NORMAL_VOL_FLAT = DoubleArray.of(0.01, 0.01, 0.01, 0.01); private static final InterpolatedNodalSurface SURFACE_FLAT = InterpolatedNodalSurface.of( METADATA, TIMES_FLAT, TENOR_FLAT, NORMAL_VOL_FLAT, INTERPOLATOR_2D); public static final NormalVolatilityExpiryTenorSwaptionProvider NORMAL_VOL_SWAPTION_PROVIDER_USD_FLAT = NormalVolatilityExpiryTenorSwaptionProvider.of( SURFACE_FLAT, USD_1Y_LIBOR3M, DayCounts.ACT_365F, VALUATION_DATE_STD, VALUATION_TIME_STD, VALUATION_ZONE_STD); // ===== Market data as of 2014-03-20 ===== private static final DoubleArray TIMES_20150320 = DoubleArray.of( 0.25, 0.25, 0.25, 0.25, 0.25, 0.50, 0.50, 0.50, 0.50, 0.50, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0, 2.0, 5.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0, 10.0, 10.0); private static final DoubleArray TENORS_20150320 = DoubleArray.of( 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0, 1.0, 2.0, 5.0, 10.0, 30.0); private static final DoubleArray NORMAL_VOL_20150320_BP = DoubleArray.of( 43.6, 65.3, 88, 87.5, 88, // 3M 55.5, 72.2, 90.3, 89.3, 88.6, // 6M 72.6, 82.7, 91.6, 89.8, 87.3, // 1Y 90.4, 91.9, 93.4, 84.7, 93.5, // 2Y 99.3, 96.8, 94.3, 88.6, 77.3, // 5Y 88.4, 85.9, 82.2, 76.7, 65.1); // 10Y private static final DoubleArray NORMAL_VOL_20150320 = NORMAL_VOL_20150320_BP.map(v -> v * BP1); private static final NodalSurface SURFACE_20150320 = InterpolatedNodalSurface.of( METADATA, TIMES_20150320, TENORS_20150320, NORMAL_VOL_20150320, INTERPOLATOR_2D); private static final LocalDate VALUATION_DATE_20150320 = LocalDate.of(2015, 3, 20); private static final LocalTime VALUATION_TIME_20150320 = LocalTime.of(18, 00); private static final ZoneId VALUATION_ZONE_20150320 = ZoneId.of("Europe/London"); public static final NormalVolatilityExpiryTenorSwaptionProvider NORMAL_VOL_SWAPTION_PROVIDER_USD_20150320 = NormalVolatilityExpiryTenorSwaptionProvider.of( SURFACE_20150320, USD_1Y_LIBOR3M, DayCounts.ACT_365F, VALUATION_DATE_20150320, VALUATION_TIME_20150320, VALUATION_ZONE_20150320); }