/**
* Copyright (C) 2015 - 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 com.opengamma.strata.basics.date.BusinessDayConventions.MODIFIED_FOLLOWING;
import static com.opengamma.strata.basics.date.BusinessDayConventions.PRECEDING;
import static com.opengamma.strata.basics.date.DayCounts.ACT_360;
import static com.opengamma.strata.basics.date.DayCounts.THIRTY_U_360;
import static com.opengamma.strata.basics.date.HolidayCalendarIds.USNY;
import static com.opengamma.strata.basics.index.IborIndices.USD_LIBOR_3M;
import static com.opengamma.strata.basics.index.IborIndices.USD_LIBOR_6M;
import static com.opengamma.strata.basics.index.OvernightIndices.USD_FED_FUND;
import static com.opengamma.strata.collect.Guavate.toImmutableMap;
import static com.opengamma.strata.product.common.PayReceive.PAY;
import static com.opengamma.strata.product.common.PayReceive.RECEIVE;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.time.LocalDate;
import java.util.function.Function;
import org.testng.annotations.Test;
import com.google.common.collect.Iterables;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.DaysAdjustment;
import com.opengamma.strata.basics.index.IborIndex;
import com.opengamma.strata.basics.schedule.Frequency;
import com.opengamma.strata.basics.schedule.PeriodicSchedule;
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.market.curve.Curve;
import com.opengamma.strata.market.curve.Curves;
import com.opengamma.strata.market.curve.InterpolatedNodalCurve;
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.PayReceive;
import com.opengamma.strata.product.swap.FixedRateCalculation;
import com.opengamma.strata.product.swap.IborRateCalculation;
import com.opengamma.strata.product.swap.NotionalSchedule;
import com.opengamma.strata.product.swap.PaymentSchedule;
import com.opengamma.strata.product.swap.RateCalculationSwapLeg;
import com.opengamma.strata.product.swap.ResolvedSwap;
import com.opengamma.strata.product.swap.Swap;
import com.opengamma.strata.product.swap.SwapLeg;
/**
* Test {@link CurveGammaCalculator}.
*/
@Test
public class CurveGammaCalculatorTest {
private static final ReferenceData REF_DATA = ReferenceData.standard();
// Data, based on RatesProviderDataSets.SINGLE_USD but different valuation date
private static final LocalDate VAL_DATE_2015_04_27 = LocalDate.of(2015, 4, 27);
private static final InterpolatedNodalCurve USD_SINGLE_CURVE = InterpolatedNodalCurve.of(
Curves.zeroRates(RatesProviderDataSets.USD_SINGLE_NAME, ACT_360),
RatesProviderDataSets.TIMES_1,
RatesProviderDataSets.RATES_1_1,
RatesProviderDataSets.INTERPOLATOR);
private static final ImmutableRatesProvider SINGLE = ImmutableRatesProvider.builder(VAL_DATE_2015_04_27)
.discountCurve(USD, USD_SINGLE_CURVE)
.overnightIndexCurve(USD_FED_FUND, USD_SINGLE_CURVE)
.iborIndexCurve(USD_LIBOR_3M, USD_SINGLE_CURVE)
.iborIndexCurve(USD_LIBOR_6M, USD_SINGLE_CURVE)
.build();
private static final Currency SINGLE_CURRENCY = Currency.USD;
// Conventions
private static final BusinessDayAdjustment BDA_MF = BusinessDayAdjustment.of(MODIFIED_FOLLOWING, USNY);
private static final BusinessDayAdjustment BDA_P = BusinessDayAdjustment.of(PRECEDING, USNY);
// Instrument
private static final ResolvedSwap SWAP =
swapUsd(LocalDate.of(2016, 6, 30), LocalDate.of(2022, 6, 30), RECEIVE, NotionalSchedule.of(USD, 10_000_000), 0.01)
.resolve(REF_DATA);
// Calculators and pricers
private static final DiscountingSwapProductPricer PRICER_SWAP = DiscountingSwapProductPricer.DEFAULT;
private static final double FD_SHIFT = 1.0E-5;
private static final CurveGammaCalculator GAMMA_CAL = CurveGammaCalculator.ofCentralDifference(FD_SHIFT);
// Constants
private static final double TOLERANCE_GAMMA = 1.0E+1;
//-------------------------------------------------------------------------
public void semiParallelGammaValue() {
ImmutableRatesProvider provider = SINGLE;
Currency curveCurrency = SINGLE_CURRENCY;
DoubleArray y = USD_SINGLE_CURVE.getYValues();
int nbNode = y.size();
DoubleArray gammaExpected = DoubleArray.of(nbNode, i -> {
double[][][] yBumped = new double[2][2][nbNode];
double[][] pv = new double[2][2];
for (int pmi = 0; pmi < 2; pmi++) {
for (int pmP = 0; pmP < 2; pmP++) {
yBumped[pmi][pmP] = y.toArray();
yBumped[pmi][pmP][i] += (pmi == 0 ? 1.0 : -1.0) * FD_SHIFT;
for (int j = 0; j < nbNode; j++) {
yBumped[pmi][pmP][j] += (pmP == 0 ? 1.0 : -1.0) * FD_SHIFT;
}
Curve curveBumped = USD_SINGLE_CURVE.withYValues(DoubleArray.copyOf(yBumped[pmi][pmP]));
ImmutableRatesProvider providerBumped = provider.toBuilder()
.discountCurves(provider.getDiscountCurves().keySet().stream()
.collect(toImmutableMap(Function.identity(), k -> curveBumped)))
.indexCurves(provider.getIndexCurves().keySet().stream()
.collect(toImmutableMap(Function.identity(), k -> curveBumped)))
.build();
pv[pmi][pmP] = PRICER_SWAP.presentValue(SWAP, providerBumped).getAmount(USD).getAmount();
}
}
return (pv[1][1] - pv[1][0] - pv[0][1] + pv[0][0]) / (4 * FD_SHIFT * FD_SHIFT);
});
CurrencyParameterSensitivity sensitivityComputed = GAMMA_CAL.calculateSemiParallelGamma(
USD_SINGLE_CURVE,
curveCurrency,
c -> buildSensitivities(c, provider));
assertEquals(sensitivityComputed.getMarketDataName(), USD_SINGLE_CURVE.getName());
DoubleArray gammaComputed = sensitivityComputed.getSensitivity();
assertTrue(gammaComputed.equalWithTolerance(gammaExpected, TOLERANCE_GAMMA));
}
// Checks that different finite difference types and shifts give similar results.
public void semiParallelGammaCoherency() {
ImmutableRatesProvider provider = SINGLE;
Curve curve = Iterables.getOnlyElement(provider.getDiscountCurves().values());
Currency curveCurrency = SINGLE_CURRENCY;
double toleranceCoherency = 1.0E+5;
CurveGammaCalculator calculatorForward5 = CurveGammaCalculator.ofForwardDifference(FD_SHIFT);
CurveGammaCalculator calculatorBackward5 = CurveGammaCalculator.ofBackwardDifference(FD_SHIFT);
CurveGammaCalculator calculatorCentral4 = CurveGammaCalculator.ofCentralDifference(1.0E-4);
DoubleArray gammaCentral5 = GAMMA_CAL.calculateSemiParallelGamma(
curve, curveCurrency, c -> buildSensitivities(c, provider)).getSensitivity();
DoubleArray gammaForward5 = calculatorForward5.calculateSemiParallelGamma(
curve, curveCurrency, c -> buildSensitivities(c, provider)).getSensitivity();
assertTrue(gammaForward5.equalWithTolerance(gammaCentral5, toleranceCoherency));
DoubleArray gammaBackward5 = calculatorBackward5.calculateSemiParallelGamma(
curve, curveCurrency, c -> buildSensitivities(c, provider)).getSensitivity();
assertTrue(gammaForward5.equalWithTolerance(gammaBackward5, toleranceCoherency));
DoubleArray gammaCentral4 = calculatorCentral4.calculateSemiParallelGamma(
curve, curveCurrency, c -> buildSensitivities(c, provider)).getSensitivity();
assertTrue(gammaForward5.equalWithTolerance(gammaCentral4, toleranceCoherency));
}
//-------------------------------------------------------------------------
private static CurrencyParameterSensitivity buildSensitivities(Curve bumpedCurve, ImmutableRatesProvider ratesProvider) {
RatesProvider bumpedRatesProvider = ratesProvider.toBuilder()
.discountCurves(ratesProvider.getDiscountCurves().keySet().stream()
.collect(toImmutableMap(Function.identity(), k -> bumpedCurve)))
.indexCurves(ratesProvider.getIndexCurves().keySet().stream()
.collect(toImmutableMap(Function.identity(), k -> bumpedCurve)))
.build();
PointSensitivities pointSensitivities = PRICER_SWAP.presentValueSensitivity(SWAP, bumpedRatesProvider).build();
CurrencyParameterSensitivities paramSensitivities = bumpedRatesProvider.parameterSensitivity(pointSensitivities);
return Iterables.getOnlyElement(paramSensitivities.getSensitivities());
}
// swap USD standard conventions- TODO: replace by a template when available
private static Swap swapUsd(LocalDate start, LocalDate end, PayReceive payReceive,
NotionalSchedule notional, double fixedRate) {
SwapLeg fixedLeg =
fixedLeg(start, end, Frequency.P6M, payReceive, notional, fixedRate, StubConvention.SHORT_INITIAL);
SwapLeg iborLeg =
iborLeg(start, end, USD_LIBOR_3M, (payReceive == PAY) ? RECEIVE : PAY, notional, StubConvention.SHORT_INITIAL);
return Swap.of(fixedLeg, iborLeg);
}
// fixed rate leg
private static SwapLeg fixedLeg(
LocalDate start, LocalDate end, Frequency frequency,
PayReceive payReceive, NotionalSchedule notional, double fixedRate, StubConvention stubConvention) {
return RateCalculationSwapLeg.builder()
.payReceive(payReceive)
.accrualSchedule(PeriodicSchedule.builder()
.startDate(start)
.endDate(end)
.frequency(frequency)
.businessDayAdjustment(BDA_MF)
.stubConvention(stubConvention)
.build())
.paymentSchedule(PaymentSchedule.builder()
.paymentFrequency(frequency)
.paymentDateOffset(DaysAdjustment.NONE)
.build())
.notionalSchedule(notional)
.calculation(FixedRateCalculation.builder()
.dayCount(THIRTY_U_360)
.rate(ValueSchedule.of(fixedRate))
.build())
.build();
}
// fixed rate leg
private static SwapLeg iborLeg(
LocalDate start, LocalDate end, IborIndex index,
PayReceive payReceive, NotionalSchedule notional, StubConvention stubConvention) {
Frequency freq = Frequency.of(index.getTenor().getPeriod());
return RateCalculationSwapLeg.builder()
.payReceive(payReceive)
.accrualSchedule(PeriodicSchedule.builder()
.startDate(start)
.endDate(end)
.frequency(freq)
.businessDayAdjustment(BDA_MF)
.stubConvention(stubConvention)
.build())
.paymentSchedule(PaymentSchedule.builder()
.paymentFrequency(freq)
.paymentDateOffset(DaysAdjustment.NONE)
.build())
.notionalSchedule(notional)
.calculation(IborRateCalculation.builder()
.index(index)
.fixingDateOffset(DaysAdjustment.ofBusinessDays(-2, index.getFixingCalendar(), BDA_P))
.build())
.build();
}
}