/**
* 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 com.opengamma.strata.basics.index.IborIndices.EUR_EURIBOR_3M;
import static com.opengamma.strata.basics.index.IborIndices.EUR_EURIBOR_6M;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import java.time.LocalDate;
import java.util.HashMap;
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.currency.Currency;
import com.opengamma.strata.basics.currency.FxRate;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.collect.io.ResourceLocator;
import com.opengamma.strata.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.data.FxRateId;
import com.opengamma.strata.data.ImmutableMarketData;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.loader.csv.FxRatesCsvLoader;
import com.opengamma.strata.loader.csv.QuotesCsvLoader;
import com.opengamma.strata.loader.csv.RatesCalibrationCsvLoader;
import com.opengamma.strata.market.curve.CurveGroupDefinition;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveNode;
import com.opengamma.strata.market.curve.NodalCurveDefinition;
import com.opengamma.strata.market.observable.IndexQuoteId;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.ResolvedTrade;
import com.opengamma.strata.product.swap.ResolvedSwapTrade;
import com.opengamma.strata.product.swap.SwapLegType;
/**
* Tests {@link SyntheticCurveCalibrator}.
*/
@Test
public class SyntheticCurveCalibratorTest {
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/";
// Group input based on FRA and basis swaps for EURIBOR3M
private static final String GROUPS_IN_EUR_FILE = "EUR-DSCONOIS-E3BS-E6IRS-group.csv";
private static final String SETTINGS_IN_EUR_FILE = "EUR-DSCONOIS-E3BS-E6IRS-settings.csv";
private static final String NODES_IN_EUR_FILE = "EUR-DSCONOIS-E3BS-E6IRS-nodes.csv";
private static final CurveGroupDefinition GROUPS_IN_EUR =
RatesCalibrationCsvLoader.load(
ResourceLocator.of(CONFIG_PATH + GROUPS_IN_EUR_FILE),
ResourceLocator.of(CONFIG_PATH + SETTINGS_IN_EUR_FILE),
ResourceLocator.of(CONFIG_PATH + NODES_IN_EUR_FILE)).get(CurveGroupName.of("EUR-DSCONOIS-E3BS-E6IRS"));
private static final String GROUPS_IN_USDEUR_FILE = "USD-EUR-DSCONOIS-L3IRS-DSCFXXCCY33-E3IRS-group.csv";
private static final String SETTINGS_IN_USDEUR_FILE = "USD-EUR-DSCONOIS-L3IRS-DSCFXXCCY33-E3IRS-settings.csv";
private static final String NODES_IN_USDEUR_FILE = "USD-EUR-DSCONOIS-L3IRS-DSCFXXCCY33-E3IRS-nodes.csv";
private static final CurveGroupDefinition GROUPS_IN_USDEUR =
RatesCalibrationCsvLoader.load(
ResourceLocator.of(CONFIG_PATH + GROUPS_IN_USDEUR_FILE),
ResourceLocator.of(CONFIG_PATH + SETTINGS_IN_USDEUR_FILE),
ResourceLocator.of(CONFIG_PATH + NODES_IN_USDEUR_FILE)).get(CurveGroupName.of("USD-EUR-DSCONOIS-L3IRS-DSCFXXCCY33-E3IRS"));
// Group with synthetic curves, all nodes based on deposit or Fixed v Floating swaps
private static final String GROUPS_SY_EUR_FILE = "FRTB-EUR-group.csv";
private static final String SETTINGS_SY_EUR_FILE = "FRTB-EUR-settings.csv";
private static final String NODES_SY_EUR_FILE = "FRTB-EUR-nodes.csv";
private static final CurveGroupDefinition GROUPS_SYN_EUR =
RatesCalibrationCsvLoader.load(
ResourceLocator.of(CONFIG_PATH + GROUPS_SY_EUR_FILE),
ResourceLocator.of(CONFIG_PATH + SETTINGS_SY_EUR_FILE),
ResourceLocator.of(CONFIG_PATH + NODES_SY_EUR_FILE)).get(CurveGroupName.of("BIMM-EUR"));
private static final String GROUPS_SY_USDEUR_FILE = "FRTB-USD-EUR-group.csv";
private static final String SETTINGS_SY_USDEUR_FILE = "FRTB-USD-EUR-settings.csv";
private static final String NODES_SY_USDEUR_FILE = "FRTB-USD-EUR-nodes.csv";
private static final CurveGroupDefinition GROUPS_SYN_USDEUR =
RatesCalibrationCsvLoader.load(
ResourceLocator.of(CONFIG_PATH + GROUPS_SY_USDEUR_FILE),
ResourceLocator.of(CONFIG_PATH + SETTINGS_SY_USDEUR_FILE),
ResourceLocator.of(CONFIG_PATH + NODES_SY_USDEUR_FILE)).get(CurveGroupName.of("FRTB-USD-EUR"));
private static final String QUOTES_EUR_FILE = "quotes-20151120-eur.csv";
private static final String QUOTES_USDEUR_FILE = "MARKET_QUOTES_EUR_USD_20151120.csv";
private static final String QUOTE_FX_FILE = "MARKET-QUOTES-FX-20151120.csv";
private static final Map<QuoteId, Double> MQ_EUR_INPUT =
QuotesCsvLoader.load(VALUATION_DATE, ImmutableList.of(ResourceLocator.of(QUOTES_PATH + QUOTES_EUR_FILE)));
private static final ImmutableMarketData MARKET_QUOTES_EUR_INPUT = ImmutableMarketData.of(VALUATION_DATE, MQ_EUR_INPUT);
private static final Map<QuoteId, Double> MQ_USDEUR_INPUT =
QuotesCsvLoader.load(VALUATION_DATE, ImmutableList.of(ResourceLocator.of(QUOTES_PATH + QUOTES_USDEUR_FILE)));
private static final Map<FxRateId, FxRate> MAP_FX =
FxRatesCsvLoader.load(VALUATION_DATE, ResourceLocator.of(QUOTES_PATH + QUOTE_FX_FILE));
private static final ImmutableMarketData MARKET_QUOTES_USDEUR_INPUT =
ImmutableMarketData.builder(VALUATION_DATE)
.addValueMap(MQ_USDEUR_INPUT).addValueMap(MAP_FX).build();
private static final Map<Index, LocalDateDoubleTimeSeries> TS_LARGE = new HashMap<>();
private static final MarketData TS_LARGE_MD;
static { // Fixing unnaturally high to see the difference in the calibration
LocalDateDoubleTimeSeries tsEur3 = LocalDateDoubleTimeSeries.builder().put(VALUATION_DATE, 0.0200).build();
LocalDateDoubleTimeSeries tsEur6 = LocalDateDoubleTimeSeries.builder().put(VALUATION_DATE, 0.0250).build();
TS_LARGE.put(EUR_EURIBOR_3M, tsEur3);
TS_LARGE.put(EUR_EURIBOR_6M, tsEur6);
TS_LARGE_MD = ImmutableMarketData.builder(VALUATION_DATE)
.addTimeSeries(IndexQuoteId.of(EUR_EURIBOR_3M), tsEur3)
.addTimeSeries(IndexQuoteId.of(EUR_EURIBOR_6M), tsEur6)
.build();
}
private static final CurveCalibrator CALIBRATOR = CurveCalibrator.standard();
private static final CalibrationMeasures MQ_MEASURES = CalibrationMeasures.MARKET_QUOTE;
private static final SyntheticCurveCalibrator CALIBRATOR_SYNTHETIC =
SyntheticCurveCalibrator.of(CALIBRATOR, MQ_MEASURES);
private static final ImmutableRatesProvider MULTICURVE_INPUT_EUR_TSEMPTY =
CALIBRATOR.calibrate(GROUPS_IN_EUR, MARKET_QUOTES_EUR_INPUT, REF_DATA);
private static final RatesProvider MULTICURVE_INPUT_EUR_TSLARGE =
CALIBRATOR.calibrate(GROUPS_IN_EUR, MARKET_QUOTES_EUR_INPUT.combinedWith(TS_LARGE_MD), REF_DATA);
private static final ImmutableRatesProvider MULTICURVE_INPUT_USDEUR_TSEMPTY =
CALIBRATOR.calibrate(GROUPS_IN_USDEUR, MARKET_QUOTES_USDEUR_INPUT, REF_DATA);
private static final double TOLERANCE_MQ = 1.0E-8;
//-------------------------------------------------------------------------
public void test_of() {
SyntheticCurveCalibrator test = SyntheticCurveCalibrator.of(CALIBRATOR, MQ_MEASURES);
assertEquals(test.getMeasures(), MQ_MEASURES);
assertEquals(test.getCalibrator(), CALIBRATOR);
assertEquals(test.toString(), "SyntheticCurveCalibrator[CurveCalibrator[ParSpread], MarketQuote]");
}
//-------------------------------------------------------------------------
// Check market data computation
public void market_data() {
CurveGroupDefinition group = GROUPS_SYN_EUR;
RatesProvider multicurveTsLarge = MULTICURVE_INPUT_EUR_TSEMPTY.toBuilder().timeSeries(TS_LARGE).build();
MarketData madTsEmpty = CALIBRATOR_SYNTHETIC.marketData(group, MULTICURVE_INPUT_EUR_TSEMPTY, REF_DATA);
MarketData madTsLarge = CALIBRATOR_SYNTHETIC.marketData(group, multicurveTsLarge, REF_DATA);
assertEquals(madTsEmpty.getValuationDate(), VALUATION_DATE);
for (NodalCurveDefinition entry : group.getCurveDefinitions()) {
ImmutableList<CurveNode> nodes = entry.getNodes();
for (CurveNode node : nodes) {
ResolvedTrade tradeTsEmpty = node.resolvedTrade(1d, madTsEmpty, REF_DATA);
double mqTsEmpty = MQ_MEASURES.value(tradeTsEmpty, MULTICURVE_INPUT_EUR_TSEMPTY);
assertEquals(mqTsEmpty, (Double) madTsEmpty.getValue(node.requirements().iterator().next()), TOLERANCE_MQ);
ResolvedTrade tradeTsLarge = node.resolvedTrade(1d, madTsLarge, REF_DATA);
double mqTsLarge = MQ_MEASURES.value(tradeTsLarge, multicurveTsLarge);
assertEquals(mqTsLarge, (Double) madTsLarge.getValue(node.requirements().iterator().next()), TOLERANCE_MQ);
// Market Quote for Fixed v ibor swaps should have changed with the fixing
if ((tradeTsLarge instanceof ResolvedSwapTrade) && // Swap Fixed v Ibor
(((ResolvedSwapTrade) tradeTsLarge)).getProduct().getLegs(SwapLegType.IBOR).size() == 1) {
assertTrue(Math.abs(mqTsEmpty - mqTsLarge) > TOLERANCE_MQ);
}
}
}
}
// Check synthetic calibration in case no time-series is present
public void calibrate_ts_empty() {
MarketData mad = CALIBRATOR_SYNTHETIC.marketData(GROUPS_SYN_EUR, MULTICURVE_INPUT_EUR_TSEMPTY, REF_DATA);
RatesProvider multicurveSyn = CALIBRATOR_SYNTHETIC.calibrate(GROUPS_SYN_EUR, MULTICURVE_INPUT_EUR_TSEMPTY, REF_DATA);
for (NodalCurveDefinition entry : GROUPS_SYN_EUR.getCurveDefinitions()) {
ImmutableList<CurveNode> nodes = entry.getNodes();
for (CurveNode node : nodes) {
ResolvedTrade trade = node.resolvedTrade(1d, mad, REF_DATA);
double mqIn = MQ_MEASURES.value(trade, MULTICURVE_INPUT_EUR_TSEMPTY);
double mqSy = MQ_MEASURES.value(trade, multicurveSyn);
assertEquals(mqIn, mqSy, TOLERANCE_MQ);
}
}
}
// Check synthetic calibration in the case of existing time-series with fixing on the valuation date
public void calibrate_ts_vd() {
SyntheticCurveCalibrator calibratorDefault = SyntheticCurveCalibrator.standard();
MarketData mad = calibratorDefault.marketData(GROUPS_SYN_EUR, MULTICURVE_INPUT_EUR_TSLARGE, REF_DATA);
RatesProvider multicurveSyn = CALIBRATOR_SYNTHETIC.calibrate(GROUPS_SYN_EUR, MULTICURVE_INPUT_EUR_TSLARGE, REF_DATA);
for (NodalCurveDefinition entry : GROUPS_SYN_EUR.getCurveDefinitions()) {
ImmutableList<CurveNode> nodes = entry.getNodes();
for (CurveNode node : nodes) {
ResolvedTrade trade = node.resolvedTrade(1d, mad, REF_DATA);
double mqIn = MQ_MEASURES.value(trade, MULTICURVE_INPUT_EUR_TSLARGE);
double mqSy = MQ_MEASURES.value(trade, multicurveSyn);
assertEquals(mqIn, mqSy, TOLERANCE_MQ);
}
}
}
// Check FX rates are transfered in multi-currency cases.
public void calibrate_xccy() {
RatesProvider multicurveSyn = CALIBRATOR_SYNTHETIC.calibrate(GROUPS_SYN_USDEUR, MULTICURVE_INPUT_USDEUR_TSEMPTY, REF_DATA);
double eurUsdInput = MULTICURVE_INPUT_USDEUR_TSEMPTY.fxRate(Currency.EUR, Currency.USD);
double eurUsdSynthetic = multicurveSyn.fxRate(Currency.EUR, Currency.USD);
assertEquals(eurUsdInput, eurUsdSynthetic, TOLERANCE_MQ);
}
//-------------------------------------------------------------------------
@Test(enabled = false) // enabled = false for standard testing. Used only to assess the performance
public void performance() {
long start, end;
int nbReps = 4;
int nbTests = 100;
for (int looprep = 0; looprep < nbReps; looprep++) {
start = System.currentTimeMillis();
int hs = 0;
for (int looptest = 0; looptest < nbTests; looptest++) {
RatesProvider multicurve =
CALIBRATOR.calibrate(GROUPS_IN_EUR, MARKET_QUOTES_EUR_INPUT.combinedWith(TS_LARGE_MD), REF_DATA);
hs += multicurve.getValuationDate().getDayOfMonth();
}
end = System.currentTimeMillis();
System.out.println("Initial curve calibration time: " + (end-start) +
" ms for " + nbTests + " calibrations. " + hs);
}
for (int looprep = 0; looprep < nbReps; looprep++) {
start = System.currentTimeMillis();
int hs = 0;
for (int looptest = 0; looptest < nbTests; looptest++) {
RatesProvider multicurve1 =
CALIBRATOR.calibrate(GROUPS_IN_EUR, MARKET_QUOTES_EUR_INPUT.combinedWith(TS_LARGE_MD), REF_DATA);
RatesProvider multicurve2 = CALIBRATOR_SYNTHETIC.calibrate(GROUPS_SYN_EUR, multicurve1, REF_DATA);
hs += multicurve2.getValuationDate().getDayOfMonth();
}
end = System.currentTimeMillis();
System.out.println("Initial + synthetic curve calibration time: " + (end-start) +
" ms for " + nbTests + " calibrations. " + hs);
}
// Calibration time of the (initial + synthetic) curves is roughly twice as long as the initial calibration on its
// own. There is almost no overhead to compute the synthetic quotes used as input to the second calibration.
}
}