/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.swaption; import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.LINEAR; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.DATA_ARRAY_FULL; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.DATA_ARRAY_SPARSE; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.DAY_COUNT; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.EXPIRIES; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.MONEYNESS; import static com.opengamma.strata.pricer.swaption.SwaptionCubeData.TENORS; import static com.opengamma.strata.product.common.PayReceive.RECEIVE; import static com.opengamma.strata.product.swap.type.FixedIborSwapConventions.EUR_FIXED_1Y_EURIBOR_6M; import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Map; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.date.BusinessDayAdjustment; import com.opengamma.strata.basics.date.BusinessDayConventions; import com.opengamma.strata.basics.date.Tenor; import com.opengamma.strata.basics.schedule.Frequency; import com.opengamma.strata.basics.schedule.PeriodicSchedule; import com.opengamma.strata.basics.schedule.RollConventions; import com.opengamma.strata.basics.schedule.StubConvention; import com.opengamma.strata.basics.value.ValueSchedule; import com.opengamma.strata.collect.array.DoubleArray; import com.opengamma.strata.collect.io.ResourceLocator; import com.opengamma.strata.collect.tuple.Pair; import com.opengamma.strata.data.ImmutableMarketData; import com.opengamma.strata.loader.csv.QuotesCsvLoader; import com.opengamma.strata.loader.csv.RatesCalibrationCsvLoader; import com.opengamma.strata.market.ValueType; import com.opengamma.strata.market.curve.CurveGroupDefinition; import com.opengamma.strata.market.curve.CurveGroupName; import com.opengamma.strata.market.observable.QuoteId; import com.opengamma.strata.market.param.CurrencyParameterSensitivities; import com.opengamma.strata.market.param.CurrencyParameterSensitivity; import com.opengamma.strata.market.sensitivity.PointSensitivities; import com.opengamma.strata.market.surface.ConstantSurface; import com.opengamma.strata.market.surface.DefaultSurfaceMetadata; import com.opengamma.strata.market.surface.Surface; import com.opengamma.strata.market.surface.interpolator.GridSurfaceInterpolator; import com.opengamma.strata.market.surface.interpolator.SurfaceInterpolator; import com.opengamma.strata.pricer.cms.SabrExtrapolationReplicationCmsLegPricer; import com.opengamma.strata.pricer.cms.SabrExtrapolationReplicationCmsPeriodPricer; import com.opengamma.strata.pricer.curve.CalibrationMeasures; import com.opengamma.strata.pricer.curve.CurveCalibrator; import com.opengamma.strata.pricer.option.TenorRawOptionData; import com.opengamma.strata.pricer.rate.RatesProvider; import com.opengamma.strata.product.cms.CmsLeg; import com.opengamma.strata.product.cms.ResolvedCmsLeg; import com.opengamma.strata.product.swap.SwapIndex; import com.opengamma.strata.product.swap.SwapIndices; /** * Tests {@link SabrSwaptionRawDataSensitivityCalculator}. */ @Test public class SabrSwaptionRawDataSensitivityCalculatorTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); /* ===== Data ===== */ private static final LocalDate CALIBRATION_DATE = LocalDate.of(2016, 2, 29); private static final ZonedDateTime CALIBRATION_TIME = CALIBRATION_DATE.atTime(10, 0).atZone(ZoneId.of("Europe/Berlin")); private static final SabrSwaptionCalibrator SABR_CALIBRATION = SabrSwaptionCalibrator.DEFAULT; private static final String BASE_DIR = "src/test/resources/"; private static final String GROUPS_FILE = "curve-config/EUR-DSCONOIS-E3BS-E6IRS-group.csv"; private static final String SETTINGS_FILE = "curve-config/EUR-DSCONOIS-E3BS-E6IRS-settings.csv"; private static final String NODES_FILE = "curve-config/EUR-DSCONOIS-E3BS-E6IRS-nodes.csv"; private static final String QUOTES_FILE = "quotes/quotes-20160229-eur.csv"; private static final CurveGroupDefinition CONFIGS = RatesCalibrationCsvLoader.load( ResourceLocator.of(BASE_DIR + GROUPS_FILE), ResourceLocator.of(BASE_DIR + SETTINGS_FILE), ResourceLocator.of(BASE_DIR + NODES_FILE)).get(CurveGroupName.of("EUR-DSCONOIS-E3BS-E6IRS")); private static final Map<QuoteId, Double> MAP_MQ = QuotesCsvLoader.load(CALIBRATION_DATE, ImmutableList.of(ResourceLocator.of(BASE_DIR + QUOTES_FILE))); private static final ImmutableMarketData MARKET_QUOTES = ImmutableMarketData.of(CALIBRATION_DATE, MAP_MQ); private static final CalibrationMeasures CALIBRATION_MEASURES = CalibrationMeasures.PAR_SPREAD; private static final CurveCalibrator CALIBRATOR = CurveCalibrator.of(1e-9, 1e-9, 100, CALIBRATION_MEASURES); private static final RatesProvider MULTICURVE = CALIBRATOR.calibrate(CONFIGS, MARKET_QUOTES, REF_DATA); private static final TenorRawOptionData DATA_RAW_FULL = SabrSwaptionCalibratorSmileTestUtils .rawData(TENORS, EXPIRIES, ValueType.SIMPLE_MONEYNESS, MONEYNESS, ValueType.NORMAL_VOLATILITY, DATA_ARRAY_FULL); private static final TenorRawOptionData DATA_RAW_SPARSE = SabrSwaptionCalibratorSmileTestUtils .rawData(TENORS, EXPIRIES, ValueType.SIMPLE_MONEYNESS, MONEYNESS, ValueType.NORMAL_VOLATILITY, DATA_ARRAY_SPARSE); private static final SurfaceInterpolator INTERPOLATOR_2D = GridSurfaceInterpolator.of(LINEAR, LINEAR); private static final SwaptionVolatilitiesName NAME_SABR = SwaptionVolatilitiesName.of("Calibrated-SABR"); private static final SabrSwaptionDefinition DEFINITION = SabrSwaptionDefinition.of(NAME_SABR, EUR_FIXED_1Y_EURIBOR_6M, DAY_COUNT, INTERPOLATOR_2D); private static final double BETA = 0.50; private static final Surface BETA_SURFACE = ConstantSurface.of("Beta", BETA) .withMetadata(DefaultSurfaceMetadata.builder() .xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION) .zValueType(ValueType.SABR_BETA).surfaceName("Beta").build()); private static final double SHIFT_SABR = 0.0300; private static final Surface SHIFT_SABR_SURFACE = ConstantSurface.of("Shift", SHIFT_SABR) .withMetadata(DefaultSurfaceMetadata.builder() .xValueType(ValueType.YEAR_FRACTION).yValueType(ValueType.YEAR_FRACTION).surfaceName("Shift").build()); private static final SabrParametersSwaptionVolatilities SABR_CALIBRATED_FULL = SABR_CALIBRATION.calibrateWithFixedBetaAndShift( DEFINITION, CALIBRATION_TIME, DATA_RAW_FULL, MULTICURVE, BETA_SURFACE, SHIFT_SABR_SURFACE); private static final SabrParametersSwaptionVolatilities SABR_CALIBRATED_SPARSE = SABR_CALIBRATION.calibrateWithFixedBetaAndShift( DEFINITION, CALIBRATION_TIME, DATA_RAW_SPARSE, MULTICURVE, BETA_SURFACE, SHIFT_SABR_SURFACE); /* ===== Trades ===== */ private static final LocalDate START = LocalDate.of(2016, 3, 7); private static final LocalDate END = LocalDate.of(2021, 3, 7); private static final Frequency FREQUENCY = Frequency.P12M; private static final BusinessDayAdjustment BUSS_ADJ_EUR = BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); private static final PeriodicSchedule SCHEDULE_EUR = PeriodicSchedule.of(START, END, FREQUENCY, BUSS_ADJ_EUR, StubConvention.NONE, RollConventions.NONE); private static final SwapIndex INDEX = SwapIndices.EUR_EURIBOR_1100_5Y; private static final double FLOOR_VALUE = 0.014; private static final ValueSchedule FLOOR_STRIKE = ValueSchedule.of(FLOOR_VALUE); private static final double NOTIONAL_VALUE = 100_000_000; private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONAL_VALUE); private static final ResolvedCmsLeg FLOOR_LEG = CmsLeg.builder() .floorSchedule(FLOOR_STRIKE) .index(INDEX) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule(SCHEDULE_EUR) .build() .resolve(REF_DATA); /* ===== Pricers ===== */ private static final double CUT_OFF_STRIKE = 0.10; private static final double MU = 2.50; private static final SabrExtrapolationReplicationCmsPeriodPricer PERIOD_PRICER = SabrExtrapolationReplicationCmsPeriodPricer.of(CUT_OFF_STRIKE, MU); private static final SabrExtrapolationReplicationCmsLegPricer LEG_PRICER = new SabrExtrapolationReplicationCmsLegPricer(PERIOD_PRICER); private static final SabrSwaptionRawDataSensitivityCalculator RDSC = SabrSwaptionRawDataSensitivityCalculator.DEFAULT; /** * Compare the AD version of the sensitivity to a finite difference parallel bump of the smile. * Full data set, no missing data. */ public void presentValueSensitivityRawDataParallelSensitivity_full() { presentValueSensitivityRawDataParallelSensitivity(SABR_CALIBRATED_FULL, DATA_RAW_FULL); } /** * Compare the AD version of the sensitivity to a finite difference parallel bump of the smile. * Sparse data set, some raw data are missing in some smiles. */ public void presentValueSensitivityRawDataParallelSensitivity_sparse() { presentValueSensitivityRawDataParallelSensitivity(SABR_CALIBRATED_SPARSE, DATA_RAW_SPARSE); } private void presentValueSensitivityRawDataParallelSensitivity( SabrParametersSwaptionVolatilities sabrCalibrated, TenorRawOptionData dataRaw) { PointSensitivities points = LEG_PRICER.presentValueSensitivityModelParamsSabr(FLOOR_LEG, MULTICURVE, sabrCalibrated).build(); CurrencyParameterSensitivities sabrParametersSurfaceSensitivities = sabrCalibrated.parameterSensitivity(points); CurrencyParameterSensitivity parallelSensitivitiesSurface = RDSC.parallelSensitivity(sabrParametersSurfaceSensitivities, sabrCalibrated); DoubleArray sensitivityArray = parallelSensitivitiesSurface.getSensitivity(); double fdShift = 1.0E-6; int surfacePointIndex = 0; for (int loopexpiry = 0; loopexpiry < EXPIRIES.size(); loopexpiry++) { for (int looptenor = 0; looptenor < TENORS.size(); looptenor++) { Tenor tenor = TENORS.get(looptenor); Pair<DoubleArray, DoubleArray> ds = dataRaw.getData(tenor).availableSmileAtExpiry(EXPIRIES.get(loopexpiry)); if (!ds.getFirst().isEmpty()) { double[] pv = new double[2]; // pv with shift up and down for (int loopsign = 0; loopsign < 2; loopsign++) { TenorRawOptionData dataShifted = SabrSwaptionCalibratorSmileTestUtils .rawDataShiftSmile( TENORS, EXPIRIES, ValueType.SIMPLE_MONEYNESS, MONEYNESS, ValueType.NORMAL_VOLATILITY, DATA_ARRAY_FULL, looptenor, loopexpiry, (2 * loopsign - 1) * fdShift); SabrParametersSwaptionVolatilities calibratedShifted = SABR_CALIBRATION.calibrateWithFixedBetaAndShift( DEFINITION, CALIBRATION_TIME, dataShifted, MULTICURVE, BETA_SURFACE, SHIFT_SABR_SURFACE); pv[loopsign] = LEG_PRICER.presentValue(FLOOR_LEG, MULTICURVE, calibratedShifted).getAmount(); } double sensitivityFd = (pv[1] - pv[0]) / (2 * fdShift); // FD sensitivity computation SabrSwaptionCalibratorSmileTestUtils.checkAcceptable(sensitivityFd, sensitivityArray.get(surfacePointIndex), 0.10, "Tenor/Expiry: " + TENORS.get(looptenor) + " / " + EXPIRIES.get(loopexpiry)); surfacePointIndex++; } } } } }