/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.measure.curve;
import static com.opengamma.strata.basics.date.DayCounts.ACT_360;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg;
import static com.opengamma.strata.collect.TestHelper.date;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.offset;
import java.time.LocalDate;
import java.time.Period;
import java.util.List;
import java.util.Map;
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.StandardId;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.FxRate;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.date.Tenor;
import com.opengamma.strata.basics.index.IborIndices;
import com.opengamma.strata.calc.marketdata.MarketDataConfig;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.data.FxRateId;
import com.opengamma.strata.data.ImmutableMarketData;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.data.MarketDataId;
import com.opengamma.strata.data.ObservableSource;
import com.opengamma.strata.data.scenario.ImmutableScenarioMarketData;
import com.opengamma.strata.data.scenario.MarketDataBox;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
import com.opengamma.strata.market.ValueType;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.curve.CurveGroup;
import com.opengamma.strata.market.curve.CurveGroupDefinition;
import com.opengamma.strata.market.curve.CurveGroupId;
import com.opengamma.strata.market.curve.CurveGroupName;
import com.opengamma.strata.market.curve.CurveId;
import com.opengamma.strata.market.curve.CurveInputs;
import com.opengamma.strata.market.curve.CurveInputsId;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.CurveNode;
import com.opengamma.strata.market.curve.DefaultCurveMetadata;
import com.opengamma.strata.market.curve.InterpolatedNodalCurveDefinition;
import com.opengamma.strata.market.curve.interpolator.CurveExtrapolators;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolators;
import com.opengamma.strata.market.curve.node.FixedIborSwapCurveNode;
import com.opengamma.strata.market.curve.node.FraCurveNode;
import com.opengamma.strata.market.curve.node.FxSwapCurveNode;
import com.opengamma.strata.market.observable.QuoteId;
import com.opengamma.strata.market.param.ParameterMetadata;
import com.opengamma.strata.measure.rate.RatesMarketDataLookup;
import com.opengamma.strata.pricer.curve.CurveCalibrator;
import com.opengamma.strata.pricer.fra.DiscountingFraTradePricer;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.pricer.swap.DiscountingSwapTradePricer;
import com.opengamma.strata.product.fra.ResolvedFraTrade;
import com.opengamma.strata.product.fx.type.FxSwapConventions;
import com.opengamma.strata.product.fx.type.FxSwapTemplate;
import com.opengamma.strata.product.swap.ResolvedSwapTrade;
/**
* Test {@link CurveGroupMarketDataFunction}.
*/
@Test
public class CurveGroupMarketDataFunctionTest {
/** The calibrator. */
private static final CurveCalibrator CALIBRATOR = CurveCalibrator.standard();
/** The maximum allowable PV when round-tripping an instrument used to calibrate a curve. */
private static final double PV_TOLERANCE = 5e-10;
/** The reference data. */
private static final ReferenceData REF_DATA = ReferenceData.standard();
/**
* Tests calibration a curve containing FRAs and pricing the curve instruments using the curve.
*/
public void roundTripFra() {
InterpolatedNodalCurveDefinition curveDefn = CurveTestUtils.fraCurveDefinition();
List<FraCurveNode> nodes = curveDefn.getNodes().stream()
.map(FraCurveNode.class::cast)
.collect(toImmutableList());
List<MarketDataId<?>> keys = nodes.stream().map(CurveTestUtils::key).collect(toImmutableList());
Map<MarketDataId<?>, Double> inputData = ImmutableMap.<MarketDataId<?>, Double>builder()
.put(keys.get(0), 0.003)
.put(keys.get(1), 0.0033)
.put(keys.get(2), 0.0037)
.put(keys.get(3), 0.0054)
.put(keys.get(4), 0.007)
.put(keys.get(5), 0.0091)
.put(keys.get(6), 0.0134)
.build();
CurveGroupName groupName = CurveGroupName.of("Curve Group");
CurveName curveName = CurveName.of("FRA Curve");
CurveInputs curveInputs = CurveInputs.of(inputData, DefaultCurveMetadata.of(curveName));
CurveGroupDefinition groupDefn = CurveGroupDefinition.builder()
.name(groupName)
.addCurve(curveDefn, Currency.USD, IborIndices.USD_LIBOR_3M)
.build();
CurveGroupMarketDataFunction function = new CurveGroupMarketDataFunction();
LocalDate valuationDate = date(2011, 3, 8);
ScenarioMarketData inputMarketData = ImmutableScenarioMarketData.builder(valuationDate)
.addValue(CurveInputsId.of(groupName, curveName, ObservableSource.NONE), curveInputs)
.build();
MarketDataBox<CurveGroup> curveGroup =
function.buildCurveGroup(groupDefn, CALIBRATOR, inputMarketData, REF_DATA, ObservableSource.NONE);
Curve curve = curveGroup.getSingleValue().findDiscountCurve(Currency.USD).get();
Map<MarketDataId<?>, Object> marketDataMap = ImmutableMap.<MarketDataId<?>, Object>builder()
.putAll(inputData)
.put(CurveId.of(groupName, curveName), curve)
.build();
MarketData marketData = ImmutableMarketData.of(valuationDate, marketDataMap);
TestMarketDataMap scenarioMarketData = new TestMarketDataMap(valuationDate, marketDataMap, ImmutableMap.of());
RatesMarketDataLookup lookup = RatesMarketDataLookup.of(groupDefn);
RatesProvider ratesProvider = lookup.ratesProvider(scenarioMarketData.scenario(0));
// The PV should be zero for an instrument used to build the curve
nodes.stream().forEach(node -> checkFraPvIsZero(node, ratesProvider, marketData));
}
public void roundTripFraAndFixedFloatSwap() {
CurveGroupName groupName = CurveGroupName.of("Curve Group");
InterpolatedNodalCurveDefinition curveDefn = CurveTestUtils.fraSwapCurveDefinition();
CurveName curveName = curveDefn.getName();
List<CurveNode> nodes = curveDefn.getNodes();
CurveGroupDefinition groupDefn = CurveGroupDefinition.builder()
.name(groupName)
.addCurve(curveDefn, Currency.USD, IborIndices.USD_LIBOR_3M)
.build();
CurveGroupMarketDataFunction function = new CurveGroupMarketDataFunction();
LocalDate valuationDate = date(2011, 3, 8);
Map<MarketDataId<?>, Double> inputData = ImmutableMap.<MarketDataId<?>, Double>builder()
.put(CurveTestUtils.key(nodes.get(0)), 0.0037)
.put(CurveTestUtils.key(nodes.get(1)), 0.0054)
.put(CurveTestUtils.key(nodes.get(2)), 0.005)
.put(CurveTestUtils.key(nodes.get(3)), 0.0087)
.put(CurveTestUtils.key(nodes.get(4)), 0.012)
.build();
CurveInputs curveInputs = CurveInputs.of(inputData, DefaultCurveMetadata.of(curveName));
ScenarioMarketData inputMarketData = ImmutableScenarioMarketData.builder(valuationDate)
.addValue(CurveInputsId.of(groupName, curveName, ObservableSource.NONE), curveInputs)
.build();
MarketDataBox<CurveGroup> curveGroup =
function.buildCurveGroup(groupDefn, CALIBRATOR, inputMarketData, REF_DATA, ObservableSource.NONE);
Curve curve = curveGroup.getSingleValue().findDiscountCurve(Currency.USD).get();
Map<MarketDataId<?>, Object> marketDataMap = ImmutableMap.<MarketDataId<?>, Object>builder()
.putAll(inputData)
.put(CurveId.of(groupName, curveName), curve)
.build();
MarketData marketData = ImmutableMarketData.of(valuationDate, marketDataMap);
TestMarketDataMap scenarioMarketData = new TestMarketDataMap(valuationDate, marketDataMap, ImmutableMap.of());
RatesMarketDataLookup lookup = RatesMarketDataLookup.of(groupDefn);
RatesProvider ratesProvider = lookup.ratesProvider(scenarioMarketData.scenario(0));
checkFraPvIsZero((FraCurveNode) nodes.get(0), ratesProvider, marketData);
checkFraPvIsZero((FraCurveNode) nodes.get(1), ratesProvider, marketData);
checkSwapPvIsZero((FixedIborSwapCurveNode) nodes.get(2), ratesProvider, marketData);
checkSwapPvIsZero((FixedIborSwapCurveNode) nodes.get(3), ratesProvider, marketData);
checkSwapPvIsZero((FixedIborSwapCurveNode) nodes.get(4), ratesProvider, marketData);
}
/**
* Tests that par rates are required for curves.
*/
public void requirements() {
FraCurveNode node1x4 = CurveTestUtils.fraNode(1, "foo");
FraCurveNode node2x5 = CurveTestUtils.fraNode(2, "foo");
List<CurveNode> nodes = ImmutableList.of(node1x4, node2x5);
CurveGroupName groupName = CurveGroupName.of("Curve Group");
CurveName curveName = CurveName.of("FRA Curve");
ObservableSource obsSource = ObservableSource.of("Vendor");
InterpolatedNodalCurveDefinition curveDefn = InterpolatedNodalCurveDefinition.builder()
.name(curveName)
.nodes(nodes)
.interpolator(CurveInterpolators.DOUBLE_QUADRATIC)
.extrapolatorLeft(CurveExtrapolators.FLAT)
.extrapolatorRight(CurveExtrapolators.FLAT)
.build();
CurveGroupDefinition groupDefn = CurveGroupDefinition.builder()
.name(groupName)
.addCurve(curveDefn, Currency.USD, IborIndices.USD_LIBOR_3M)
.build();
MarketDataConfig marketDataConfig = MarketDataConfig.builder()
.add(groupName, groupDefn)
.build();
CurveGroupMarketDataFunction function = new CurveGroupMarketDataFunction();
CurveGroupId curveGroupId = CurveGroupId.of(groupName, obsSource);
MarketDataRequirements requirements = function.requirements(curveGroupId, marketDataConfig);
assertThat(requirements.getNonObservables()).contains(CurveInputsId.of(groupName, curveName, obsSource));
}
public void metadata() {
CurveGroupName groupName = CurveGroupName.of("Curve Group");
InterpolatedNodalCurveDefinition fraCurveDefn = CurveTestUtils.fraCurveDefinition();
List<CurveNode> fraNodes = fraCurveDefn.getNodes();
CurveGroupDefinition groupDefn = CurveGroupDefinition.builder()
.name(groupName)
.addForwardCurve(fraCurveDefn, IborIndices.USD_LIBOR_3M)
.build();
MarketDataConfig marketDataConfig = MarketDataConfig.builder()
.add(groupName, groupDefn)
.build();
CurveGroupId curveGroupId = CurveGroupId.of(groupName);
Map<MarketDataId<?>, Double> fraInputData = ImmutableMap.<MarketDataId<?>, Double>builder()
.put(CurveTestUtils.key(fraNodes.get(0)), 0.003)
.put(CurveTestUtils.key(fraNodes.get(1)), 0.0033)
.put(CurveTestUtils.key(fraNodes.get(2)), 0.0037)
.put(CurveTestUtils.key(fraNodes.get(3)), 0.0054)
.put(CurveTestUtils.key(fraNodes.get(4)), 0.007)
.put(CurveTestUtils.key(fraNodes.get(5)), 0.0091)
.put(CurveTestUtils.key(fraNodes.get(6)), 0.0134).build();
LocalDate valuationDate = date(2011, 3, 8);
CurveInputs fraCurveInputs = CurveInputs.of(fraInputData, fraCurveDefn.metadata(valuationDate, REF_DATA));
ScenarioMarketData marketData = ImmutableScenarioMarketData.builder(valuationDate)
.addValue(CurveInputsId.of(groupName, fraCurveDefn.getName(), ObservableSource.NONE), fraCurveInputs)
.build();
CurveGroupMarketDataFunction function = new CurveGroupMarketDataFunction();
MarketDataBox<CurveGroup> curveGroup = function.build(curveGroupId, marketDataConfig, marketData, REF_DATA);
// Check the FRA curve identifiers are the expected tenors
Curve forwardCurve = curveGroup.getSingleValue().findForwardCurve(IborIndices.USD_LIBOR_3M).get();
List<ParameterMetadata> forwardMetadata = forwardCurve.getMetadata().getParameterMetadata().get();
List<Object> forwardTenors = forwardMetadata.stream()
.map(ParameterMetadata::getIdentifier)
.collect(toImmutableList());
List<Tenor> expectedForwardTenors =
ImmutableList.of(
Tenor.TENOR_4M,
Tenor.TENOR_5M,
Tenor.TENOR_6M,
Tenor.TENOR_9M,
Tenor.TENOR_12M,
Tenor.ofMonths(15),
Tenor.ofMonths(21));
assertThat(forwardTenors).isEqualTo(expectedForwardTenors);
List<ParameterMetadata> expectedForwardMetadata = fraNodes.stream()
.map(node -> node.metadata(valuationDate, REF_DATA))
.collect(toImmutableList());
assertThat(forwardMetadata).isEqualTo(expectedForwardMetadata);
}
//-------------------------------------------------------------------------
public void duplicateInputDataKeys() {
FxSwapTemplate template1 = FxSwapTemplate.of(Period.ofMonths(1), FxSwapConventions.EUR_USD);
FxSwapTemplate template2 = FxSwapTemplate.of(Period.ofMonths(2), FxSwapConventions.EUR_USD);
QuoteId pointsKey1a = QuoteId.of(StandardId.of("test", "1a"));
QuoteId pointsKey1b = QuoteId.of(StandardId.of("test", "1b"));
QuoteId pointsKey2a = QuoteId.of(StandardId.of("test", "2a"));
QuoteId pointsKey2b = QuoteId.of(StandardId.of("test", "2b"));
FxSwapCurveNode node1a = FxSwapCurveNode.of(template1, pointsKey1a);
FxSwapCurveNode node1b = FxSwapCurveNode.of(template2, pointsKey1b);
FxSwapCurveNode node2 = FxSwapCurveNode.of(template1, pointsKey2a);
FxSwapCurveNode node2b = FxSwapCurveNode.of(template2, pointsKey2b);
CurveName curveName1 = CurveName.of("curve1");
InterpolatedNodalCurveDefinition curve1 = InterpolatedNodalCurveDefinition.builder()
.name(curveName1)
.nodes(node1a, node1b)
.xValueType(ValueType.YEAR_FRACTION)
.yValueType(ValueType.ZERO_RATE)
.dayCount(ACT_360)
.interpolator(CurveInterpolators.LINEAR)
.extrapolatorLeft(CurveExtrapolators.LINEAR)
.extrapolatorRight(CurveExtrapolators.LINEAR)
.build();
CurveName curveName2 = CurveName.of("curve2");
InterpolatedNodalCurveDefinition curve2 = InterpolatedNodalCurveDefinition.builder()
.name(curveName2)
.nodes(node2, node2b)
.xValueType(ValueType.YEAR_FRACTION)
.yValueType(ValueType.ZERO_RATE)
.dayCount(ACT_360)
.interpolator(CurveInterpolators.LINEAR)
.extrapolatorLeft(CurveExtrapolators.LINEAR)
.extrapolatorRight(CurveExtrapolators.LINEAR)
.build();
CurveGroupName curveGroupName = CurveGroupName.of("group");
CurveGroupDefinition groupDefinition = CurveGroupDefinition.builder()
.name(curveGroupName)
.addDiscountCurve(curve1, Currency.EUR)
.addDiscountCurve(curve2, Currency.USD)
.build();
CurveGroupMarketDataFunction fn = new CurveGroupMarketDataFunction();
Map<MarketDataId<?>, Object> marketDataMap1 = ImmutableMap.of(
FxRateId.of(Currency.EUR, Currency.USD),
FxRate.of(Currency.EUR, Currency.USD, 1.01),
pointsKey1a, 0.1d,
pointsKey1b, 0.2d);
Map<MarketDataId<?>, Object> marketDataMap2 = ImmutableMap.of(
FxRateId.of(Currency.EUR, Currency.USD),
FxRate.of(Currency.EUR, Currency.USD, 1.01),
pointsKey2a, 0.1d,
pointsKey2b, 0.2d);
CurveInputs curveInputs1 = CurveInputs.of(marketDataMap1, DefaultCurveMetadata.of("curve1"));
CurveInputs curveInputs2 = CurveInputs.of(marketDataMap2, DefaultCurveMetadata.of("curve2"));
ImmutableScenarioMarketData marketData = ImmutableScenarioMarketData.builder(LocalDate.of(2011, 3, 8))
.addValue(CurveInputsId.of(curveGroupName, curveName1, ObservableSource.NONE), curveInputs1)
.addValue(CurveInputsId.of(curveGroupName, curveName2, ObservableSource.NONE), curveInputs2)
.build();
fn.buildCurveGroup(groupDefinition, CALIBRATOR, marketData, REF_DATA, ObservableSource.NONE);
// This has a duplicate key with a different value which should fail
Map<MarketDataId<?>, Object> badMarketDataMap = ImmutableMap.of(
FxRateId.of(Currency.EUR, Currency.USD), FxRate.of(Currency.EUR, Currency.USD, 1.02),
pointsKey2a, 0.2d);
CurveInputs badCurveInputs = CurveInputs.of(badMarketDataMap, DefaultCurveMetadata.of("curve2"));
ScenarioMarketData badMarketData = ImmutableScenarioMarketData.builder(LocalDate.of(2011, 3, 8))
.addValue(CurveInputsId.of(curveGroupName, curveName1, ObservableSource.NONE), curveInputs1)
.addValue(CurveInputsId.of(curveGroupName, curveName2, ObservableSource.NONE), badCurveInputs)
.build();
String msg = "Multiple unequal values found for identifier .*\\. Values: .* and .*";
assertThrowsIllegalArg(
() -> fn.buildCurveGroup(groupDefinition, CALIBRATOR, badMarketData, REF_DATA, ObservableSource.NONE), msg);
}
//-----------------------------------------------------------------------------------------------------------
private void checkFraPvIsZero(
FraCurveNode node,
RatesProvider ratesProvider,
MarketData marketDataMap) {
ResolvedFraTrade trade = node.resolvedTrade(1d, marketDataMap, REF_DATA);
CurrencyAmount currencyAmount = DiscountingFraTradePricer.DEFAULT.presentValue(trade, ratesProvider);
double pv = currencyAmount.getAmount();
assertThat(pv).isCloseTo(0, offset(PV_TOLERANCE));
}
private void checkSwapPvIsZero(
FixedIborSwapCurveNode node,
RatesProvider ratesProvider,
MarketData marketDataMap) {
ResolvedSwapTrade trade = node.resolvedTrade(1d, marketDataMap, REF_DATA);
MultiCurrencyAmount amount = DiscountingSwapTradePricer.DEFAULT.presentValue(trade, ratesProvider);
double pv = amount.getAmount(Currency.USD).getAmount();
assertThat(pv).isCloseTo(0, offset(PV_TOLERANCE));
}
}