/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.capfloor; import static com.opengamma.strata.basics.currency.Currency.GBP; import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; import static com.opengamma.strata.basics.index.IborIndices.GBP_LIBOR_3M; import static com.opengamma.strata.basics.index.IborIndices.USD_LIBOR_3M; import static com.opengamma.strata.collect.TestHelper.coverBeanEquals; import static com.opengamma.strata.collect.TestHelper.coverImmutableBean; import static com.opengamma.strata.collect.TestHelper.date; import static com.opengamma.strata.collect.TestHelper.dateUtc; import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.LINEAR; import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.TIME_SQUARE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.time.LocalDate; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.List; import org.testng.annotations.Test; import com.opengamma.strata.collect.DoubleArrayMath; import com.opengamma.strata.collect.array.DoubleArray; import com.opengamma.strata.market.curve.ConstantCurve; import com.opengamma.strata.market.curve.CurveName; import com.opengamma.strata.market.option.SimpleStrike; import com.opengamma.strata.market.param.CurrencyParameterSensitivity; import com.opengamma.strata.market.surface.InterpolatedNodalSurface; import com.opengamma.strata.market.surface.SurfaceMetadata; import com.opengamma.strata.market.surface.Surfaces; import com.opengamma.strata.market.surface.interpolator.GridSurfaceInterpolator; import com.opengamma.strata.market.surface.interpolator.SurfaceInterpolator; import com.opengamma.strata.pricer.common.GenericVolatilitySurfaceYearFractionParameterMetadata; import com.opengamma.strata.pricer.impl.option.BlackFormulaRepository; import com.opengamma.strata.product.common.PutCall; /** * Test {@link ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities}. */ @Test public class ShiftedBlackIborCapletFloorletExpiryStrikeVolatilitiesTest { private static final SurfaceInterpolator INTERPOLATOR_2D = GridSurfaceInterpolator.of(LINEAR, LINEAR); private static final DoubleArray TIME = DoubleArray.of(0.25, 0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0); private static final DoubleArray STRIKE = DoubleArray.of(-0.005, 0.005, 0.01, 0.025, -0.005, 0.005, 0.01, 0.025, -0.005, 0.005, 0.01, 0.025); private static final DoubleArray VOL = DoubleArray.of(0.14, 0.14, 0.13, 0.12, 0.12, 0.13, 0.12, 0.11, 0.1, 0.12, 0.11, 0.1); private static final SurfaceMetadata METADATA; static { List<GenericVolatilitySurfaceYearFractionParameterMetadata> list = new ArrayList<>(); int nData = TIME.size(); for (int i = 0; i < nData; ++i) { GenericVolatilitySurfaceYearFractionParameterMetadata parameterMetadata = GenericVolatilitySurfaceYearFractionParameterMetadata.of(TIME.get(i), SimpleStrike.of(STRIKE.get(i))); list.add(parameterMetadata); } METADATA = Surfaces.blackVolatilityByExpiryStrike("CAP_VOL", ACT_365F).withParameterMetadata(list); } private static final InterpolatedNodalSurface SURFACE = InterpolatedNodalSurface.of(METADATA, TIME, STRIKE, VOL, INTERPOLATOR_2D); private static final double SHIFT = 0.02; private static final ConstantCurve CURVE = ConstantCurve.of("shift parameter", SHIFT); private static final LocalDate VAL_DATE = date(2015, 2, 17); private static final LocalTime VAL_TIME = LocalTime.of(13, 45); private static final ZoneId LONDON_ZONE = ZoneId.of("Europe/London"); private static final ZonedDateTime VAL_DATE_TIME = VAL_DATE.atTime(VAL_TIME).atZone(LONDON_ZONE); private static final ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities VOLS = ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of(GBP_LIBOR_3M, VAL_DATE_TIME, SURFACE, CURVE); 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_STRIKE = new double[] {-0.01, 0.003, 0.016, 0.032}; private static final double[] TEST_SENSITIVITY = new double[] {1.0, -34.0, 12.0, 0.1}; private static final double TEST_FORWARD = 0.015; // not used internally private static final double TOLERANCE_VOL = 1.0E-10; public void test_getter() { assertEquals(VOLS.getValuationDate(), VAL_DATE); assertEquals(VOLS.getIndex(), GBP_LIBOR_3M); assertEquals(VOLS.getSurface(), SURFACE); assertEquals(VOLS.getParameterCount(), TIME.size()); assertEquals(VOLS.findData(CURVE.getName()).get(), CURVE); assertEquals(VOLS.findData(SURFACE.getName()).get(), SURFACE); assertFalse(VOLS.findData(CurveName.of("foo")).isPresent()); int nParams = VOLS.getParameterCount(); double newValue = 152d; for (int i = 0; i < nParams; ++i) { assertEquals(VOLS.getParameter(i), SURFACE.getParameter(i)); assertEquals(VOLS.getParameterMetadata(i), SURFACE.getParameterMetadata(i)); assertEquals(VOLS.withParameter(i, newValue), ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of( GBP_LIBOR_3M, VAL_DATE_TIME, SURFACE.withParameter(i, newValue), CURVE)); assertEquals(VOLS.withPerturbation((n, v, m) -> 2d * v), ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of( GBP_LIBOR_3M, VAL_DATE_TIME, SURFACE.withPerturbation((n, v, m) -> 2d * v), CURVE)); } } public void test_price_formula() { double sampleVol = 0.2; for (int i = 0; i < NB_TEST; i++) { double expiryTime = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]); for (int j = 0; j < NB_TEST; j++) { for (PutCall putCall : new PutCall[] {PutCall.CALL, PutCall.PUT}) { assertEquals( VOLS.price(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol), BlackFormulaRepository.price( TEST_FORWARD + SHIFT, TEST_STRIKE[j] + SHIFT, expiryTime, sampleVol, putCall.isCall())); assertEquals( VOLS.priceDelta(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol), BlackFormulaRepository.delta( TEST_FORWARD + SHIFT, TEST_STRIKE[j] + SHIFT, expiryTime, sampleVol, putCall.isCall())); assertEquals( VOLS.priceGamma(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol), BlackFormulaRepository.gamma(TEST_FORWARD + SHIFT, TEST_STRIKE[j] + SHIFT, expiryTime, sampleVol)); assertEquals( VOLS.priceTheta(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol), BlackFormulaRepository.driftlessTheta(TEST_FORWARD + SHIFT, TEST_STRIKE[j] + SHIFT, expiryTime, sampleVol)); assertEquals( VOLS.priceVega(expiryTime, putCall, TEST_STRIKE[j], TEST_FORWARD, sampleVol), BlackFormulaRepository.vega(TEST_FORWARD + SHIFT, TEST_STRIKE[j] + SHIFT, expiryTime, sampleVol)); } } } } public void test_relativeTime() { double test1 = VOLS.relativeTime(VAL_DATE_TIME); assertEquals(test1, 0d); double test2 = VOLS.relativeTime(date(2018, 2, 17).atStartOfDay(LONDON_ZONE)); double test3 = VOLS.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 = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]); for (int j = 0; j < NB_TEST; ++j) { double volExpected = SURFACE.zValue(expiryTime, TEST_STRIKE[j] + SHIFT); double volComputed = VOLS.volatility(TEST_OPTION_EXPIRY[i], TEST_STRIKE[j], 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++) { for (int k = 0; k < NB_TEST; k++) { double expiryTime = VOLS.relativeTime(TEST_OPTION_EXPIRY[i]); IborCapletFloorletSensitivity point = IborCapletFloorletSensitivity.of( VOLS.getName(), expiryTime, TEST_STRIKE[k], TEST_FORWARD, GBP, TEST_SENSITIVITY[i]); double[] sensFd = new double[nData]; 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, TIME, STRIKE, volDataUp, INTERPOLATOR_2D); InterpolatedNodalSurface paramDw = InterpolatedNodalSurface.of(METADATA, TIME, STRIKE, volDataDw, INTERPOLATOR_2D); ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities provUp = ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of(GBP_LIBOR_3M, VAL_DATE_TIME, paramUp, CURVE); ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities provDw = ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of(GBP_LIBOR_3M, VAL_DATE_TIME, paramDw, CURVE); double volUp = provUp.volatility(TEST_OPTION_EXPIRY[i], TEST_STRIKE[k], TEST_FORWARD); double volDw = provDw.volatility(TEST_OPTION_EXPIRY[i], TEST_STRIKE[k], TEST_FORWARD); double fd = 0.5 * (volUp - volDw) / eps; sensFd[j] = fd * TEST_SENSITIVITY[i]; } CurrencyParameterSensitivity sensActual = VOLS.parameterSensitivity(point).getSensitivities().get(0); double[] computed = sensActual.getSensitivity().toArray(); assertTrue(DoubleArrayMath.fuzzyEquals(computed, sensFd, eps)); } } } //------------------------------------------------------------------------- public void coverage() { coverImmutableBean(VOLS); ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities vols = ShiftedBlackIborCapletFloorletExpiryStrikeVolatilities.of( USD_LIBOR_3M, VAL_DATE_TIME.plusMonths(1), InterpolatedNodalSurface.of(METADATA, TIME, STRIKE, VOL, GridSurfaceInterpolator.of(TIME_SQUARE, LINEAR)), ConstantCurve.of("shift", 0.05)); coverBeanEquals(VOLS, vols); } }