/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.sensitivity; import static com.opengamma.strata.basics.currency.Currency.USD; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.time.LocalDate; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.function.BiFunction; import java.util.function.Function; import org.testng.annotations.Test; import com.google.common.collect.ImmutableMap; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.index.IborIndex; import com.opengamma.strata.basics.index.Index; import com.opengamma.strata.collect.ArgChecker; import com.opengamma.strata.collect.array.DoubleArray; import com.opengamma.strata.collect.array.DoubleMatrix; import com.opengamma.strata.market.curve.Curve; import com.opengamma.strata.market.curve.InterpolatedNodalCurve; import com.opengamma.strata.market.curve.NodalCurve; import com.opengamma.strata.market.param.CrossGammaParameterSensitivities; import com.opengamma.strata.market.param.CrossGammaParameterSensitivity; 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.pricer.datasets.RatesProviderDataSets; import com.opengamma.strata.pricer.rate.ImmutableRatesProvider; import com.opengamma.strata.pricer.rate.RatesProvider; import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer; import com.opengamma.strata.product.common.BuySell; import com.opengamma.strata.product.swap.ResolvedSwap; import com.opengamma.strata.product.swap.type.FixedIborSwapConventions; /** * Test {@link CurveGammaCalculator} cross-gamma. */ @Test public class CurveGammaCalculatorCrossGammaTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); private static final double EPS = 1.0e-6; private static final double TOL = 1.0e-14; private static final CurveGammaCalculator FORWARD = CurveGammaCalculator.ofForwardDifference(EPS * 0.1); private static final CurveGammaCalculator CENTRAL = CurveGammaCalculator.ofCentralDifference(EPS); private static final CurveGammaCalculator BACKWARD = CurveGammaCalculator.ofBackwardDifference(EPS * 0.1); public void sensitivity_single_curve() { CrossGammaParameterSensitivities forward = FORWARD.calculateCrossGammaIntraCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); CrossGammaParameterSensitivities central = CENTRAL.calculateCrossGammaIntraCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); CrossGammaParameterSensitivities backward = BACKWARD.calculateCrossGammaIntraCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); DoubleArray times = RatesProviderDataSets.TIMES_1; for (CrossGammaParameterSensitivities sensi : new CrossGammaParameterSensitivities[] {forward, central, backward}) { CurrencyParameterSensitivities diagonalComputed = sensi.diagonal(); assertEquals(sensi.size(), 1); assertEquals(diagonalComputed.size(), 1); DoubleMatrix s = sensi.getSensitivities().get(0).getSensitivity(); assertEquals(s.columnCount(), times.size()); for (int i = 0; i < times.size(); i++) { for (int j = 0; j < times.size(); j++) { double expected = 32d * times.get(i) * times.get(j); assertEquals(s.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } } // no difference for single curve CrossGammaParameterSensitivities forwardCross = FORWARD.calculateCrossGammaCrossCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); assertTrue(forward.equalWithTolerance(forwardCross, TOL)); CrossGammaParameterSensitivities centralCross = CENTRAL.calculateCrossGammaCrossCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); assertTrue(central.equalWithTolerance(centralCross, TOL)); CrossGammaParameterSensitivities backwardCross = BACKWARD.calculateCrossGammaCrossCurve(RatesProviderDataSets.SINGLE_USD, this::sensiFn); assertTrue(backward.equalWithTolerance(backwardCross, TOL)); } public void sensitivity_intra_multi_curve() { CrossGammaParameterSensitivities sensiComputed = CENTRAL.calculateCrossGammaIntraCurve(RatesProviderDataSets.MULTI_CPI_USD, this::sensiFn); DoubleArray times1 = RatesProviderDataSets.TIMES_1; DoubleArray times2 = RatesProviderDataSets.TIMES_2; DoubleArray times3 = RatesProviderDataSets.TIMES_3; DoubleArray times4 = RatesProviderDataSets.TIMES_4; assertEquals(sensiComputed.size(), 4); DoubleMatrix s1 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_DSC_NAME, USD).getSensitivity(); assertEquals(s1.columnCount(), times1.size()); for (int i = 0; i < times1.size(); i++) { for (int j = 0; j < times1.size(); j++) { double expected = 8d * times1.get(i) * times1.get(j); assertEquals(s1.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS * 10d); } } DoubleMatrix s2 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L3_NAME, USD).getSensitivity(); assertEquals(s2.columnCount(), times2.size()); for (int i = 0; i < times2.size(); i++) { for (int j = 0; j < times2.size(); j++) { double expected = 2d * times2.get(i) * times2.get(j); assertEquals(s2.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } DoubleMatrix s3 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L6_NAME, USD).getSensitivity(); assertEquals(s3.columnCount(), times3.size()); for (int i = 0; i < times3.size(); i++) { for (int j = 0; j < times3.size(); j++) { double expected = 2d * times3.get(i) * times3.get(j); assertEquals(s3.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } DoubleMatrix s4 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_CPI_NAME, USD).getSensitivity(); assertEquals(s4.columnCount(), times4.size()); for (int i = 0; i < times4.size(); i++) { for (int j = 0; j < times4.size(); j++) { double expected = 2d * times4.get(i) * times4.get(j); assertEquals(s4.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } } public void sensitivity_multi_curve_empty() { CrossGammaParameterSensitivities sensiComputed = CENTRAL.calculateCrossGammaIntraCurve(RatesProviderDataSets.MULTI_CPI_USD, this::sensiModFn); DoubleArray times2 = RatesProviderDataSets.TIMES_2; DoubleArray times3 = RatesProviderDataSets.TIMES_3; assertEquals(sensiComputed.size(), 2); DoubleMatrix s2 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L3_NAME, USD).getSensitivity(); assertEquals(s2.columnCount(), times2.size()); for (int i = 0; i < times2.size(); i++) { for (int j = 0; j < times2.size(); j++) { double expected = 2d * times2.get(i) * times2.get(j); assertEquals(s2.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } DoubleMatrix s3 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L6_NAME, USD).getSensitivity(); assertEquals(s3.columnCount(), times3.size()); for (int i = 0; i < times3.size(); i++) { for (int j = 0; j < times3.size(); j++) { double expected = 2d * times3.get(i) * times3.get(j); assertEquals(s3.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } Optional<CrossGammaParameterSensitivity> oisSensi = sensiComputed.findSensitivity(RatesProviderDataSets.USD_DSC_NAME, USD); assertFalse(oisSensi.isPresent()); Optional<CrossGammaParameterSensitivity> priceIndexSensi = sensiComputed.findSensitivity(RatesProviderDataSets.USD_CPI_NAME, USD); assertFalse(priceIndexSensi.isPresent()); } public void sensitivity_cross_multi_curve() { CrossGammaParameterSensitivities sensiComputed = CENTRAL.calculateCrossGammaCrossCurve(RatesProviderDataSets.MULTI_CPI_USD, this::sensiFn); DoubleArray times1 = RatesProviderDataSets.TIMES_1; DoubleArray times2 = RatesProviderDataSets.TIMES_2; DoubleArray times3 = RatesProviderDataSets.TIMES_3; DoubleArray times4 = RatesProviderDataSets.TIMES_4; int paramsTotal = times1.size() + times2.size() + times3.size() + times4.size(); double[] timesTotal = new double[paramsTotal]; DoubleArray times1Twice = times1.multipliedBy(2d); System.arraycopy(times4.toArray(), 0, timesTotal, 0, times4.size()); System.arraycopy(times1Twice.toArray(), 0, timesTotal, times4.size(), times1.size()); System.arraycopy(times2.toArray(), 0, timesTotal, times1.size() + times4.size(), times2.size()); System.arraycopy(times3.toArray(), 0, timesTotal, times1.size() + times2.size() + times4.size(), times3.size()); assertEquals(sensiComputed.size(), 4); DoubleMatrix s1 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_DSC_NAME, USD).getSensitivity(); assertEquals(s1.columnCount(), paramsTotal); for (int i = 0; i < times1.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 4d * times1.get(i) * timesTotal[j]; assertEquals(s1.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS * 10d); } } DoubleMatrix s2 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L3_NAME, USD).getSensitivity(); assertEquals(s2.columnCount(), paramsTotal); for (int i = 0; i < times2.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 2d * times2.get(i) * timesTotal[j]; assertEquals(s2.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS * 10d); } } DoubleMatrix s3 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L6_NAME, USD).getSensitivity(); assertEquals(s3.columnCount(), paramsTotal); for (int i = 0; i < times3.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 2d * times3.get(i) * timesTotal[j]; assertEquals(s3.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS * 10d); } } DoubleMatrix s4 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_CPI_NAME, USD).getSensitivity(); assertEquals(s4.columnCount(), paramsTotal); for (int i = 0; i < times4.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 2d * times4.get(i) * timesTotal[j]; assertEquals(s4.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS * 20d); } } } public void sensitivity_cross_multi_curve_empty() { CrossGammaParameterSensitivities sensiComputed = CENTRAL.calculateCrossGammaCrossCurve(RatesProviderDataSets.MULTI_CPI_USD, this::sensiModFn); DoubleArray times2 = RatesProviderDataSets.TIMES_2; DoubleArray times3 = RatesProviderDataSets.TIMES_3; int paramsTotal = times2.size() + times3.size(); double[] timesTotal = new double[paramsTotal]; System.arraycopy(times2.toArray(), 0, timesTotal, 0, times2.size()); System.arraycopy(times3.toArray(), 0, timesTotal, times2.size(), times3.size()); assertEquals(sensiComputed.size(), 2); DoubleMatrix s2 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L3_NAME, USD).getSensitivity(); assertEquals(s2.columnCount(), paramsTotal); for (int i = 0; i < times2.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 2d * times2.get(i) * timesTotal[j]; assertEquals(s2.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } DoubleMatrix s3 = sensiComputed.getSensitivity(RatesProviderDataSets.USD_L6_NAME, USD).getSensitivity(); assertEquals(s3.columnCount(), paramsTotal); for (int i = 0; i < times3.size(); i++) { for (int j = 0; j < paramsTotal; j++) { double expected = 2d * times3.get(i) * timesTotal[j]; assertEquals(s3.get(i, j), expected, Math.max(Math.abs(expected), 1d) * EPS); } } Optional<CrossGammaParameterSensitivity> oisSensi = sensiComputed.findSensitivity(RatesProviderDataSets.USD_DSC_NAME, USD); assertFalse(oisSensi.isPresent()); Optional<CrossGammaParameterSensitivity> priceIndexSensi = sensiComputed.findSensitivity(RatesProviderDataSets.USD_CPI_NAME, USD); assertFalse(priceIndexSensi.isPresent()); } // test diagonal part against finite difference approximation computed from pv public void swap_exampleTest() { LocalDate start = LocalDate.of(2014, 3, 10); LocalDate end = LocalDate.of(2021, 3, 10); double notional = 1.0e6; ResolvedSwap swap = FixedIborSwapConventions.USD_FIXED_6M_LIBOR_3M .toTrade(RatesProviderDataSets.VAL_DATE_2014_01_22, start, end, BuySell.BUY, notional, 0.005) .getProduct() .resolve(REF_DATA); DiscountingSwapProductPricer pricer = DiscountingSwapProductPricer.DEFAULT; Function<ImmutableRatesProvider, CurrencyAmount> pvFunction = p -> pricer.presentValue(swap, USD, p); Function<ImmutableRatesProvider, CurrencyParameterSensitivities> sensiFunction = p -> { PointSensitivities sensi = pricer.presentValueSensitivity(swap, p).build(); return p.parameterSensitivity(sensi); }; CurrencyParameterSensitivities expected = sensitivityDiagonal(RatesProviderDataSets.MULTI_CPI_USD, pvFunction); CurrencyParameterSensitivities computed = CENTRAL.calculateCrossGammaIntraCurve(RatesProviderDataSets.MULTI_CPI_USD, sensiFunction).diagonal(); assertTrue(computed.equalWithTolerance(expected, Math.sqrt(EPS) * notional)); CurrencyParameterSensitivities computedFromCross = CENTRAL.calculateCrossGammaCrossCurve(RatesProviderDataSets.MULTI_CPI_USD, sensiFunction).diagonal(); assertTrue(computed.equalWithTolerance(computedFromCross, TOL)); } //------------------------------------------------------------------------- private CurrencyParameterSensitivities sensiFn(ImmutableRatesProvider provider) { CurrencyParameterSensitivities sensi = CurrencyParameterSensitivities.empty(); // Currency ImmutableMap<Currency, Curve> mapCurrency = provider.getDiscountCurves(); for (Entry<Currency, Curve> entry : mapCurrency.entrySet()) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); double sumSqrt = sum(provider); sensi = sensi.combinedWith(CurrencyParameterSensitivity.of(curveInt.getName(), USD, DoubleArray.of(curveInt.getParameterCount(), i -> 2d * sumSqrt * curveInt.getXValues().get(i)))); } // Index ImmutableMap<Index, Curve> mapIndex = provider.getIndexCurves(); for (Entry<Index, Curve> entry : mapIndex.entrySet()) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); double sumSqrt = sum(provider); sensi = sensi.combinedWith(CurrencyParameterSensitivity.of(curveInt.getName(), USD, DoubleArray.of(curveInt.getParameterCount(), i -> 2d * sumSqrt * curveInt.getXValues().get(i)))); } return sensi; } // modified sensitivity function - sensitivities are computed only for ibor index curves private CurrencyParameterSensitivities sensiModFn(ImmutableRatesProvider provider) { CurrencyParameterSensitivities sensi = CurrencyParameterSensitivities.empty(); // Index ImmutableMap<Index, Curve> mapIndex = provider.getIndexCurves(); for (Entry<Index, Curve> entry : mapIndex.entrySet()) { if (entry.getKey() instanceof IborIndex) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); double sumSqrt = sumMod(provider); sensi = sensi.combinedWith(CurrencyParameterSensitivity.of(curveInt.getName(), USD, DoubleArray.of(curveInt.getParameterCount(), i -> 2d * sumSqrt * curveInt.getXValues().get(i)))); } } return sensi; } private double sum(ImmutableRatesProvider provider) { double result = 0.0; // Currency ImmutableMap<Currency, Curve> mapCurrency = provider.getDiscountCurves(); for (Entry<Currency, Curve> entry : mapCurrency.entrySet()) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); result += sumSingle(curveInt); } // Index ImmutableMap<Index, Curve> mapIndex = provider.getIndexCurves(); for (Entry<Index, Curve> entry : mapIndex.entrySet()) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); result += sumSingle(curveInt); } return result; } private double sumMod(ImmutableRatesProvider provider) { double result = 0.0; // Index ImmutableMap<Index, Curve> mapIndex = provider.getIndexCurves(); for (Entry<Index, Curve> entry : mapIndex.entrySet()) { if (entry.getKey() instanceof IborIndex) { InterpolatedNodalCurve curveInt = checkInterpolated(entry.getValue()); result += sumSingle(curveInt); } } return result; } private double sumSingle(NodalCurve curveInt) { double result = 0.0; DoubleArray x = curveInt.getXValues(); DoubleArray y = curveInt.getYValues(); int nbNodePoint = x.size(); for (int i = 0; i < nbNodePoint; i++) { result += x.get(i) * y.get(i); } return result; } // check that the curve is InterpolatedNodalCurve private InterpolatedNodalCurve checkInterpolated(Curve curve) { ArgChecker.isTrue(curve instanceof InterpolatedNodalCurve, "Curve should be a InterpolatedNodalCurve"); return (InterpolatedNodalCurve) curve; } //------------------------------------------------------------------------- // computes diagonal part private CurrencyParameterSensitivities sensitivityDiagonal( RatesProvider provider, Function<ImmutableRatesProvider, CurrencyAmount> valueFn) { ImmutableRatesProvider immProv = provider.toImmutableRatesProvider(); CurrencyAmount valueInit = valueFn.apply(immProv); CurrencyParameterSensitivities discounting = sensitivity( immProv, immProv.getDiscountCurves(), (base, bumped) -> base.toBuilder().discountCurves(bumped).build(), valueFn, valueInit); CurrencyParameterSensitivities forward = sensitivity( immProv, immProv.getIndexCurves(), (base, bumped) -> base.toBuilder().indexCurves(bumped).build(), valueFn, valueInit); return discounting.combinedWith(forward); } // computes the sensitivity with respect to the curves private <T> CurrencyParameterSensitivities sensitivity( ImmutableRatesProvider provider, Map<T, Curve> baseCurves, BiFunction<ImmutableRatesProvider, Map<T, Curve>, ImmutableRatesProvider> storeBumpedFn, Function<ImmutableRatesProvider, CurrencyAmount> valueFn, CurrencyAmount valueInit) { CurrencyParameterSensitivities result = CurrencyParameterSensitivities.empty(); for (Entry<T, Curve> entry : baseCurves.entrySet()) { Curve curve = entry.getValue(); DoubleArray sensitivity = DoubleArray.of(curve.getParameterCount(), i -> { Curve dscUp = curve.withParameter(i, curve.getParameter(i) + EPS); Curve dscDw = curve.withParameter(i, curve.getParameter(i) - EPS); HashMap<T, Curve> mapUp = new HashMap<>(baseCurves); HashMap<T, Curve> mapDw = new HashMap<>(baseCurves); mapUp.put(entry.getKey(), dscUp); mapDw.put(entry.getKey(), dscDw); ImmutableRatesProvider providerUp = storeBumpedFn.apply(provider, mapUp); ImmutableRatesProvider providerDw = storeBumpedFn.apply(provider, mapDw); return (valueFn.apply(providerUp).getAmount() + valueFn.apply(providerDw).getAmount() - 2d * valueInit.getAmount()) / EPS / EPS; }); result = result.combinedWith(curve.createParameterSensitivity(valueInit.getCurrency(), sensitivity)); } return result; } }