/** * 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.EUR; import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; import static com.opengamma.strata.basics.index.IborIndices.EUR_EURIBOR_6M; import static com.opengamma.strata.product.swap.type.FixedIborSwapConventions.EUR_FIXED_1Y_EURIBOR_6M; import static com.opengamma.strata.product.swap.type.FixedOvernightSwapConventions.EUR_FIXED_1Y_EONIA_OIS; import static org.testng.Assert.assertEquals; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.StandardId; import com.opengamma.strata.basics.date.Tenor; import com.opengamma.strata.collect.array.DoubleArray; import com.opengamma.strata.collect.array.DoubleMatrix; import com.opengamma.strata.collect.io.ResourceLocator; 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.curve.CurveInfoType; import com.opengamma.strata.market.curve.CurveName; import com.opengamma.strata.market.curve.CurveParameterSize; import com.opengamma.strata.market.curve.DefaultCurveMetadata; import com.opengamma.strata.market.curve.InterpolatedNodalCurve; import com.opengamma.strata.market.curve.JacobianCalibrationMatrix; import com.opengamma.strata.market.curve.interpolator.CurveExtrapolators; import com.opengamma.strata.market.curve.interpolator.CurveInterpolators; import com.opengamma.strata.market.observable.QuoteId; import com.opengamma.strata.market.param.CurrencyParameterSensitivities; import com.opengamma.strata.market.param.TenorParameterMetadata; import com.opengamma.strata.pricer.curve.CalibrationMeasures; import com.opengamma.strata.pricer.curve.CurveCalibrator; import com.opengamma.strata.pricer.deposit.DiscountingIborFixingDepositProductPricer; 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.ResolvedTrade; import com.opengamma.strata.product.common.BuySell; import com.opengamma.strata.product.deposit.ResolvedIborFixingDepositTrade; import com.opengamma.strata.product.deposit.type.IborFixingDepositConvention; import com.opengamma.strata.product.swap.ResolvedSwapTrade; /** * Tests {@link CurveSensitivityUtils}. */ @Test public class CurveSensitivityUtilsJacobianTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); private static final LocalDate VALUATION_DATE = LocalDate.of(2015, 11, 20); // Configuration and data stored in csv to avoid long code description of the input data private static final String CONFIG_PATH = "src/test/resources/curve-config/"; private static final String QUOTES_PATH = "src/test/resources/quotes/"; // Quotes private static final CurveCalibrator CALIBRATOR = CurveCalibrator.standard(); private static final String QUOTES_FILE = "quotes-20151120-eur.csv"; private static final Map<QuoteId, Double> MQ_INPUT = QuotesCsvLoader.load(VALUATION_DATE, ImmutableList.of(ResourceLocator.of(QUOTES_PATH + QUOTES_FILE))); private static final ImmutableMarketData MARKET_QUOTES_INPUT = ImmutableMarketData.of(VALUATION_DATE, MQ_INPUT); // Group input based on IRS for EURIBOR6M public static final CurveName EUR_SINGLE_NAME = CurveName.of("EUR-ALLIRS"); private static final String GROUPS_IN_1_FILE = "EUR-ALLIRS-group.csv"; private static final String SETTINGS_IN_1_FILE = "EUR-ALLIRS-settings.csv"; private static final String NODES_IN_1_FILE = "EUR-ALLIRS-STD-nodes.csv"; private static final CurveGroupDefinition GROUPS_IN_1 = RatesCalibrationCsvLoader.load( ResourceLocator.of(CONFIG_PATH + GROUPS_IN_1_FILE), ResourceLocator.of(CONFIG_PATH + SETTINGS_IN_1_FILE), ResourceLocator.of(CONFIG_PATH + NODES_IN_1_FILE)).get(CurveGroupName.of("EUR-SINGLE")); private static final RatesProvider MULTICURVE_EUR_SINGLE_CALIBRATED = CALIBRATOR.calibrate(GROUPS_IN_1, MARKET_QUOTES_INPUT, REF_DATA); public static final CalibrationMeasures MARKET_QUOTE = CalibrationMeasures.MARKET_QUOTE; public static final DiscountingSwapProductPricer PRICER_SWAP_PRODUCT = DiscountingSwapProductPricer.DEFAULT; public static final DiscountingIborFixingDepositProductPricer PRICER_IBORFIX_PRODUCT = DiscountingIborFixingDepositProductPricer.DEFAULT; public static final DoubleArray TIME_EUR = DoubleArray.of(1.0d/365.0d, 1.0d/12d, 0.25, 0.50, 1.00, 2.00, 3.00, 4.00, 5.00, 7.00, 10.0, 15.0, 20.0, 30.0); public static final ImmutableRatesProvider MULTICURVE_EUR_SINGLE_INPUT; static { Tenor[] tenors = new Tenor[] {Tenor.TENOR_1D, Tenor.TENOR_1M, Tenor.TENOR_3M, Tenor.TENOR_6M, Tenor.TENOR_1Y, Tenor.TENOR_2Y, Tenor.TENOR_3Y, Tenor.TENOR_4Y, Tenor.TENOR_5Y, Tenor.TENOR_7Y, Tenor.TENOR_10Y, Tenor.TENOR_15Y, Tenor.TENOR_20Y, Tenor.TENOR_30Y}; List<TenorParameterMetadata> metadataList = new ArrayList<>(); for(int looptenor=0; looptenor< tenors.length; looptenor++) { metadataList.add(TenorParameterMetadata.of(tenors[looptenor])); } DoubleArray rate_eur = DoubleArray.of(0.0160, 0.0165, 0.0155, 0.0155, 0.0155, 0.0150, 0.0150, 0.0160, 0.0165, 0.0155, 0.0155, 0.0155, 0.0150, 0.0140); InterpolatedNodalCurve curve_single_eur = InterpolatedNodalCurve.builder() .metadata(DefaultCurveMetadata.builder() .curveName(EUR_SINGLE_NAME) .parameterMetadata(metadataList) .dayCount(ACT_365F) .xValueType(ValueType.YEAR_FRACTION) .yValueType(ValueType.ZERO_RATE).build()) .xValues(TIME_EUR) .yValues(rate_eur) .extrapolatorLeft(CurveExtrapolators.FLAT) .extrapolatorRight(CurveExtrapolators.FLAT) .interpolator(CurveInterpolators.LINEAR) .build(); MULTICURVE_EUR_SINGLE_INPUT = ImmutableRatesProvider.builder(VALUATION_DATE) .discountCurve(EUR, curve_single_eur) .iborIndexCurve(EUR_EURIBOR_6M, curve_single_eur) .build(); } public static final List<CurveParameterSize> LIST_CURVE_NAMES_1 = new ArrayList<>(); static { LIST_CURVE_NAMES_1.add(CurveParameterSize.of(EUR_SINGLE_NAME, TIME_EUR.size())); } private static final String OG_TICKER = "OG-Ticker"; private static final Tenor[] TENORS_STD_1 = new Tenor[] {Tenor.TENOR_2Y, Tenor.TENOR_5Y, Tenor.TENOR_10Y, Tenor.TENOR_30Y}; private static final String[] TICKERS_STD_1 = new String[] {"EUR-IRS6M-2Y", "EUR-IRS6M-5Y", "EUR-IRS6M-10Y", "EUR-IRS6M-30Y"}; private static final double TOLERANCE_JAC = 1.0E-6; private static final double TOLERANCE_JAC_APPROX = 1.0E-2; /** * Calibrate a single curve to 4 points. Use the resulting calibrated curves as starting point of the computation * of a Jacobian. Compare the direct Jacobian and the one reconstructed from trades. */ public void direct_one_curve() { /* Create trades */ List<ResolvedTrade> trades = new ArrayList<>(); List<LocalDate> nodeDates = new ArrayList<>(); for (int looptenor = 0; looptenor < TENORS_STD_1.length; looptenor++) { ResolvedSwapTrade t0 = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA); double rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_SINGLE_CALIBRATED); ResolvedSwapTrade t = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA); nodeDates.add(t.getProduct().getEndDate()); trades.add(t); } /* Par rate sensitivity */ Function<ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) -> MULTICURVE_EUR_SINGLE_CALIBRATED.parameterSensitivity( PRICER_SWAP_PRODUCT.parRateSensitivity(((ResolvedSwapTrade) t).getProduct(), MULTICURVE_EUR_SINGLE_CALIBRATED).build()); DoubleMatrix jiComputed = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(LIST_CURVE_NAMES_1, trades, sensitivityFunction); DoubleMatrix jiExpected = MULTICURVE_EUR_SINGLE_CALIBRATED.findData(EUR_SINGLE_NAME).get().getMetadata().findInfo(CurveInfoType.JACOBIAN).get() .getJacobianMatrix(); /* Comparison */ assertEquals(jiComputed.rowCount() , jiExpected.rowCount()); assertEquals(jiComputed.columnCount() , jiExpected.columnCount()); for(int i=0; i<jiComputed.rowCount(); i++) { for(int j=0; j<jiComputed.columnCount(); j++) { assertEquals(jiComputed.get(i, j), jiExpected.get(i, j), TOLERANCE_JAC); } } } /** * Start from a generic zero-coupon curve. Compute the (inverse) Jacobian matrix using linear projection to a small * number of points and the Jacobian utility. Compare the direct Jacobian obtained by calibrating a curve * based on the trades with market quotes computed from the zero-coupon curve. */ public void with_rebucketing_one_curve() { /* Create trades */ List<ResolvedTrade> trades = new ArrayList<>(); List<LocalDate> nodeDates = new ArrayList<>(); double[] marketQuotes = new double[TENORS_STD_1.length]; for (int looptenor = 0; looptenor < TENORS_STD_1.length; looptenor++) { ResolvedSwapTrade t0 = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA); marketQuotes[looptenor] = MARKET_QUOTE.value(t0, MULTICURVE_EUR_SINGLE_INPUT); ResolvedSwapTrade t = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_1[looptenor], BuySell.BUY, 1.0, marketQuotes[looptenor], REF_DATA).resolve(REF_DATA); nodeDates.add(t.getProduct().getEndDate()); trades.add(t); } Function<ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) -> CurveSensitivityUtils.linearRebucketing( MULTICURVE_EUR_SINGLE_INPUT.parameterSensitivity( PRICER_SWAP_PRODUCT.parRateSensitivity(((ResolvedSwapTrade) t).getProduct(), MULTICURVE_EUR_SINGLE_INPUT).build()), nodeDates, VALUATION_DATE); /* Market quotes for comparison */ Map<QuoteId, Double> mqCmp = new HashMap<>(); for (int looptenor = 0; looptenor < TENORS_STD_1.length; looptenor++) { mqCmp.put(QuoteId.of(StandardId.of(OG_TICKER, TICKERS_STD_1[looptenor])), marketQuotes[looptenor]); } ImmutableMarketData marketQuotesObject = ImmutableMarketData.of(VALUATION_DATE, mqCmp); RatesProvider multicurveCmp = CALIBRATOR.calibrate(GROUPS_IN_1, marketQuotesObject, REF_DATA); /* Comparison */ DoubleMatrix jiComputed = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(LIST_CURVE_NAMES_1, trades, sensitivityFunction); DoubleMatrix jiExpected = multicurveCmp .findData(EUR_SINGLE_NAME).get().getMetadata().findInfo(CurveInfoType.JACOBIAN).get().getJacobianMatrix(); assertEquals(jiComputed.rowCount(), jiExpected.rowCount()); assertEquals(jiComputed.columnCount(), jiExpected.columnCount()); for (int i = 0; i < jiComputed.rowCount(); i++) { for (int j = 0; j < jiComputed.columnCount(); j++) { assertEquals(jiComputed.get(i, j), jiExpected.get(i, j), TOLERANCE_JAC_APPROX); // The comparison is not perfect due to the incoherences introduced by the re-bucketing } } } // Group input based on OIS for DSC-ON and IRS for EURIBOR6M public static final CurveName EUR_DSCON_OIS = CurveName.of("EUR-DSCON-OIS"); public static final CurveName EUR_EURIBOR6M_IRS = CurveName.of("EUR-EURIBOR6M-IRS"); private static final String GROUPS_IN_2_FILE = "EUR-DSCONOIS-E6IRS-group.csv"; private static final String SETTINGS_IN_2_FILE = "EUR-DSCONOIS-E6IRS-settings.csv"; private static final String NODES_IN_2_FILE = "EUR-DSCONOIS-E6IRS-STD-nodes.csv"; private static final CurveGroupDefinition GROUPS_IN_2 = RatesCalibrationCsvLoader.load( ResourceLocator.of(CONFIG_PATH + GROUPS_IN_2_FILE), ResourceLocator.of(CONFIG_PATH + SETTINGS_IN_2_FILE), ResourceLocator.of(CONFIG_PATH + NODES_IN_2_FILE)).get(CurveGroupName.of("EUR-DSCONOIS-E6IRS")); private static final RatesProvider MULTICURVE_EUR_2_CALIBRATED = CALIBRATOR.calibrate(GROUPS_IN_2, MARKET_QUOTES_INPUT, REF_DATA); private static final Tenor[] TENORS_STD_2_OIS = new Tenor[] { Tenor.TENOR_1M, Tenor.TENOR_3M, Tenor.TENOR_6M, Tenor.TENOR_1Y, Tenor.TENOR_2Y, Tenor.TENOR_5Y, Tenor.TENOR_10Y, Tenor.TENOR_30Y}; private static final Tenor[] TENORS_STD_2_IRS = new Tenor[] { Tenor.TENOR_1Y, Tenor.TENOR_2Y, Tenor.TENOR_5Y, Tenor.TENOR_10Y, Tenor.TENOR_30Y}; /** * Calibrate a single curve to 4 points. Use the resulting calibrated curves as starting point of the computation * of a Jacobian. Compare the direct Jacobian and the one reconstructed from trades. */ public void direct_two_curves() { JacobianCalibrationMatrix jiObject = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_DSCON_OIS).get().getMetadata().findInfo(CurveInfoType.JACOBIAN).get(); ImmutableList<CurveParameterSize> order = jiObject.getOrder(); // To obtain the order of the curves in the jacobian /* Create trades */ List<ResolvedTrade> tradesDsc = new ArrayList<>(); for (int looptenor = 0; looptenor < TENORS_STD_2_OIS.length; looptenor++) { ResolvedSwapTrade t0 = EUR_FIXED_1Y_EONIA_OIS .createTrade(VALUATION_DATE, TENORS_STD_2_OIS[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA); double rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_2_CALIBRATED); ResolvedSwapTrade t = EUR_FIXED_1Y_EONIA_OIS .createTrade(VALUATION_DATE, TENORS_STD_2_OIS[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA); tradesDsc.add(t); } List<ResolvedTrade> tradesE3 = new ArrayList<>(); // Fixing IborFixingDepositConvention c = IborFixingDepositConvention.of(EUR_EURIBOR_6M); ResolvedIborFixingDepositTrade fix0 = c.createTrade(VALUATION_DATE, EUR_EURIBOR_6M.getTenor().getPeriod(), BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA); double rateFixing = MARKET_QUOTE.value(fix0, MULTICURVE_EUR_2_CALIBRATED); ResolvedIborFixingDepositTrade fix = c.createTrade(VALUATION_DATE, EUR_EURIBOR_6M.getTenor().getPeriod(), BuySell.BUY, 1.0, rateFixing, REF_DATA).resolve(REF_DATA); tradesE3.add(fix); // IRS for (int looptenor = 0; looptenor < TENORS_STD_2_IRS.length; looptenor++) { ResolvedSwapTrade t0 = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_2_IRS[looptenor], BuySell.BUY, 1.0, 0.0, REF_DATA).resolve(REF_DATA); double rate = MARKET_QUOTE.value(t0, MULTICURVE_EUR_2_CALIBRATED); ResolvedSwapTrade t = EUR_FIXED_1Y_EURIBOR_6M .createTrade(VALUATION_DATE, TENORS_STD_2_IRS[looptenor], BuySell.BUY, 1.0, rate, REF_DATA).resolve(REF_DATA); tradesE3.add(t); } List<ResolvedTrade> trades = new ArrayList<>(); if (order.get(0).getName().equals(EUR_DSCON_OIS)) { trades.addAll(tradesDsc); trades.addAll(tradesE3); } else { trades.addAll(tradesE3); trades.addAll(tradesDsc); } /* Par rate sensitivity */ Function<ResolvedTrade, CurrencyParameterSensitivities> sensitivityFunction = (t) -> MULTICURVE_EUR_2_CALIBRATED.parameterSensitivity( (t instanceof ResolvedSwapTrade) ? PRICER_SWAP_PRODUCT.parRateSensitivity( ((ResolvedSwapTrade) t).getProduct(), MULTICURVE_EUR_2_CALIBRATED).build() : PRICER_IBORFIX_PRODUCT.parRateSensitivity( ((ResolvedIborFixingDepositTrade) t).getProduct(), MULTICURVE_EUR_2_CALIBRATED)); DoubleMatrix jiComputed = CurveSensitivityUtils.jacobianFromMarketQuoteSensitivities(order, trades, sensitivityFunction); DoubleMatrix jiExpectedDsc = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_DSCON_OIS).get() .getMetadata().getInfo(CurveInfoType.JACOBIAN).getJacobianMatrix(); DoubleMatrix jiExpectedE3 = MULTICURVE_EUR_2_CALIBRATED.findData(EUR_EURIBOR6M_IRS).get() .getMetadata().getInfo(CurveInfoType.JACOBIAN).getJacobianMatrix(); /* Comparison */ assertEquals(jiComputed.rowCount(), jiExpectedDsc.rowCount() + jiExpectedE3.rowCount()); assertEquals(jiComputed.columnCount(), jiExpectedDsc.columnCount()); assertEquals(jiComputed.columnCount(), jiExpectedE3.columnCount()); int shiftDsc = order.get(0).getName().equals(EUR_DSCON_OIS) ? 0 : jiExpectedE3.rowCount(); for (int i = 0; i < jiExpectedDsc.rowCount(); i++) { for (int j = 0; j < jiExpectedDsc.columnCount(); j++) { assertEquals(jiComputed.get(i + shiftDsc, j), jiExpectedDsc.get(i, j), TOLERANCE_JAC); } } int shiftE3 = order.get(0).getName().equals(EUR_DSCON_OIS) ? jiExpectedDsc.rowCount() : 0; for (int i = 0; i < jiExpectedE3.rowCount(); i++) { for (int j = 0; j < jiExpectedDsc.columnCount(); j++) { assertEquals(jiComputed.get(i + shiftE3, j), jiExpectedE3.get(i, j), TOLERANCE_JAC); } } } }