/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.curve; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.time.LocalDate; import java.time.Period; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.date.Tenor; import com.opengamma.strata.collect.array.DoubleArray; 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.curve.Curve; 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.CurveNode; import com.opengamma.strata.market.curve.CurveParameterSize; import com.opengamma.strata.market.curve.NodalCurveDefinition; 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.pricer.rate.ImmutableRatesProvider; import com.opengamma.strata.pricer.sensitivity.MarketQuoteSensitivityCalculator; import com.opengamma.strata.pricer.sensitivity.NotionalEquivalentCalculator; import com.opengamma.strata.pricer.swap.DiscountingSwapTradePricer; import com.opengamma.strata.product.ResolvedTrade; import com.opengamma.strata.product.Trade; import com.opengamma.strata.product.common.BuySell; import com.opengamma.strata.product.swap.ResolvedSwapTrade; import com.opengamma.strata.product.swap.type.ThreeLegBasisSwapConventions; /** * Test the notional equivalent computation based on present value sensitivity to quote in * the calibrated curves by {@link CurveCalibrator}. */ @Test public class CalibrationNotionalEquivalentTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); private static final LocalDate VALUATION_DATE = LocalDate.of(2016, 2, 29); 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 CalibrationMeasures CALIBRATION_MEASURES = CalibrationMeasures.PAR_SPREAD; private static final CurveCalibrator CALIBRATOR = CurveCalibrator.of(1e-9, 1e-9, 100, CALIBRATION_MEASURES); private static final CalibrationMeasures PV_MEASURES = CalibrationMeasures.of( "PresentValue", PresentValueCalibrationMeasure.FRA_PV, PresentValueCalibrationMeasure.IBOR_FIXING_DEPOSIT_PV, PresentValueCalibrationMeasure.IBOR_FUTURE_PV, PresentValueCalibrationMeasure.SWAP_PV, PresentValueCalibrationMeasure.TERM_DEPOSIT_PV); private static final DiscountingSwapTradePricer PRICER_SWAP_TRADE = DiscountingSwapTradePricer.DEFAULT; private static final MarketQuoteSensitivityCalculator MQSC = MarketQuoteSensitivityCalculator.DEFAULT; private static final NotionalEquivalentCalculator NEC = NotionalEquivalentCalculator.DEFAULT; private static final ResourceLocator QUOTES_RESOURCES = ResourceLocator.of(BASE_DIR + QUOTES_FILE); private static final ImmutableMap<QuoteId, Double> QUOTES = QuotesCsvLoader.load(VALUATION_DATE, QUOTES_RESOURCES); private static final ImmutableMarketData MARKET_QUOTES = ImmutableMarketData.of(VALUATION_DATE, QUOTES); private static final CurveGroupDefinition GROUP_DEFINITION = 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 CurveGroupDefinition GROUP_DEFINITION_NO_INFO = GROUP_DEFINITION.toBuilder() .computeJacobian(false).computePvSensitivityToMarketQuote(false).build(); private static final CurveGroupDefinition GROUP_DEFINITION_PV_SENSI = GROUP_DEFINITION.toBuilder() .computeJacobian(true).computePvSensitivityToMarketQuote(true).build(); private static final double TOLERANCE_PV = 1.0E-8; private static final double TOLERANCE_PV_DELTA = 1.0E-2; public void check_pv_with_measures() { ImmutableRatesProvider multicurve = CALIBRATOR.calibrate(GROUP_DEFINITION, MARKET_QUOTES, REF_DATA); // the trades used for calibration List<ResolvedTrade> trades = new ArrayList<>(); ImmutableList<NodalCurveDefinition> curveGroups = GROUP_DEFINITION.getCurveDefinitions(); for (NodalCurveDefinition entry : curveGroups) { ImmutableList<CurveNode> nodes = entry.getNodes(); for (CurveNode node : nodes) { trades.add(node.resolvedTrade(1d, MARKET_QUOTES, REF_DATA)); } } // Check PV = 0 for (ResolvedTrade trade : trades) { double pv = PV_MEASURES.value(trade, multicurve); assertEquals(pv, 0.0, TOLERANCE_PV); } } public void check_pv_sensitivity() { ImmutableRatesProvider multicurve = CALIBRATOR.calibrate(GROUP_DEFINITION_PV_SENSI, MARKET_QUOTES, REF_DATA); // the trades used for calibration Map<CurveName, List<Trade>> trades = new HashMap<>(); Map<CurveName, List<ResolvedTrade>> resolvedTrades = new HashMap<>(); ImmutableList<NodalCurveDefinition> curveGroups = GROUP_DEFINITION.getCurveDefinitions(); ImmutableList.Builder<CurveParameterSize> builder = ImmutableList.builder(); for (NodalCurveDefinition entry : curveGroups) { ImmutableList<CurveNode> nodes = entry.getNodes(); List<Trade> tradesCurve = new ArrayList<>(); List<ResolvedTrade> resolvedTradesCurve = new ArrayList<>(); for (CurveNode node : nodes) { tradesCurve.add(node.trade(1d, MARKET_QUOTES, REF_DATA)); resolvedTradesCurve.add(node.resolvedTrade(1d, MARKET_QUOTES, REF_DATA)); } trades.put(entry.getName(), tradesCurve); resolvedTrades.put(entry.getName(), resolvedTradesCurve); builder.add(entry.toCurveParameterSize()); } ImmutableList<CurveParameterSize> order = builder.build(); // order of the curves // Check CurveInfo present and sensitivity as expected Map<CurveName, DoubleArray> mqsGroup = new HashMap<>(); int nodeIndex = 0; for (CurveParameterSize cps : order) { int nbParameters = cps.getParameterCount(); double[] mqsCurve = new double[nbParameters]; for (int looptrade = 0; looptrade < nbParameters; looptrade++) { DoubleArray mqsNode = PV_MEASURES.derivative(resolvedTrades.get(cps.getName()).get(looptrade), multicurve, order); mqsCurve[looptrade] = mqsNode.get(nodeIndex); nodeIndex++; } Optional<Curve> curve = multicurve.findData(cps.getName()); DoubleArray pvSensitivityExpected = DoubleArray.ofUnsafe(mqsCurve); mqsGroup.put(cps.getName(), pvSensitivityExpected); assertTrue(curve.isPresent()); assertTrue(curve.get().getMetadata().findInfo(CurveInfoType.PV_SENSITIVITY_TO_MARKET_QUOTE).isPresent()); DoubleArray pvSensitivityMetadata = curve.get().getMetadata().findInfo(CurveInfoType.PV_SENSITIVITY_TO_MARKET_QUOTE).get(); assertTrue(pvSensitivityExpected.equalWithTolerance(pvSensitivityMetadata, 1.0E-10)); } } public void check_equivalent_notional() { ImmutableRatesProvider multicurve = CALIBRATOR.calibrate(GROUP_DEFINITION_PV_SENSI, MARKET_QUOTES, REF_DATA); // Create notional equivalent for a basis trade ResolvedSwapTrade trade = ThreeLegBasisSwapConventions.EUR_FIXED_1Y_EURIBOR_3M_EURIBOR_6M .createTrade(VALUATION_DATE, Period.ofMonths(7), Tenor.TENOR_6Y, BuySell.SELL, 1_000_000, 0.03, REF_DATA) .resolve(REF_DATA); PointSensitivities pts = PRICER_SWAP_TRADE.presentValueSensitivity(trade, multicurve); CurrencyParameterSensitivities ps = multicurve.parameterSensitivity(pts); CurrencyParameterSensitivities mqs = MQSC.sensitivity(ps, multicurve); CurrencyParameterSensitivities notionalEquivalent = NEC.notionalEquivalent(mqs, multicurve); // Check metadata are same as market quote sensitivities. for(CurrencyParameterSensitivity sensi: mqs.getSensitivities()){ assertEquals(notionalEquivalent.getSensitivity(sensi.getMarketDataName(), sensi.getCurrency()).getParameterMetadata(), sensi.getParameterMetadata()); } // Check sensitivity: trade sensitivity = sum(notional equivalent sensitivities) int totalNbParameters = 0; Map<CurveName, List<ResolvedTrade>> equivalentTrades = new HashMap<>(); ImmutableList<NodalCurveDefinition> curveGroups = GROUP_DEFINITION.getCurveDefinitions(); ImmutableList.Builder<CurveParameterSize> builder = ImmutableList.builder(); for (NodalCurveDefinition entry : curveGroups) { totalNbParameters += entry.getParameterCount(); DoubleArray notionalCurve = notionalEquivalent.getSensitivity(entry.getName(), Currency.EUR).getSensitivity(); ImmutableList<CurveNode> nodes = entry.getNodes(); List<ResolvedTrade> resolvedTradesCurve = new ArrayList<>(); for (int i = 0; i < nodes.size(); i++) { resolvedTradesCurve.add(nodes.get(i).resolvedTrade(notionalCurve.get(i), MARKET_QUOTES, REF_DATA)); } equivalentTrades.put(entry.getName(), resolvedTradesCurve); builder.add(entry.toCurveParameterSize()); } ImmutableList<CurveParameterSize> order = builder.build(); // order of the curves DoubleArray totalSensitivity = DoubleArray.filled(totalNbParameters); for (Entry<CurveName, List<ResolvedTrade>> entry : equivalentTrades.entrySet()) { for (ResolvedTrade t : entry.getValue()) { totalSensitivity = totalSensitivity.plus(PV_MEASURES.derivative(t, multicurve, order)); } } DoubleArray instrumentSensi = PV_MEASURES.derivative(trade, multicurve, order); assertTrue(totalSensitivity.equalWithTolerance(instrumentSensi, TOLERANCE_PV_DELTA)); } @SuppressWarnings("unused") @Test(enabled = false) public void performance() { long start, end; int nbRep = 5; int nbTests = 10; for (int looprep = 0; looprep < nbRep; looprep++) { System.out.println("Calibration time"); start = System.currentTimeMillis(); for (int i = 0; i < nbTests; i++) { ImmutableRatesProvider multicurve1 = CALIBRATOR.calibrate(GROUP_DEFINITION_NO_INFO, MARKET_QUOTES, REF_DATA); } end = System.currentTimeMillis(); System.out.println(" |--> calibration only: " + (end - start) + " ms for " + nbTests + " runs."); start = System.currentTimeMillis(); for (int i = 0; i < nbTests; i++) { ImmutableRatesProvider multicurve1 = CALIBRATOR.calibrate(GROUP_DEFINITION, MARKET_QUOTES, REF_DATA); } end = System.currentTimeMillis(); System.out.println(" |--> calibration and Jacobian: " + (end - start) + " ms for " + nbTests + " runs."); start = System.currentTimeMillis(); for (int i = 0; i < nbTests; i++) { ImmutableRatesProvider multicurve1 = CALIBRATOR.calibrate(GROUP_DEFINITION_PV_SENSI, MARKET_QUOTES, REF_DATA); } end = System.currentTimeMillis(); System.out.println(" |--> calibration, Jacobian and PV sensi MQ: " + (end - start) + " ms for " + nbTests + " runs."); } } }