/** * 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.collect.CollectProjectAssertions.assertThat; import static com.opengamma.strata.collect.TestHelper.date; import static com.opengamma.strata.measure.StandardComponents.marketDataFactory; import static com.opengamma.strata.measure.curve.CurveTestUtils.fixedIborSwapNode; import static com.opengamma.strata.measure.curve.CurveTestUtils.fraNode; import static com.opengamma.strata.measure.curve.CurveTestUtils.id; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.offset; import java.time.LocalDate; 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.google.common.util.concurrent.MoreExecutors; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.date.DayCounts; import com.opengamma.strata.basics.date.Tenor; import com.opengamma.strata.basics.index.IborIndices; import com.opengamma.strata.calc.CalculationRules; import com.opengamma.strata.calc.Column; import com.opengamma.strata.calc.Results; import com.opengamma.strata.calc.marketdata.MarketDataConfig; import com.opengamma.strata.calc.marketdata.MarketDataFactory; import com.opengamma.strata.calc.marketdata.MarketDataRequirements; import com.opengamma.strata.calc.runner.CalculationFunctions; import com.opengamma.strata.calc.runner.CalculationTaskRunner; import com.opengamma.strata.calc.runner.CalculationTasks; import com.opengamma.strata.collect.result.Result; import com.opengamma.strata.data.ImmutableMarketData; import com.opengamma.strata.data.MarketData; import com.opengamma.strata.data.ObservableId; 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.CurveName; import com.opengamma.strata.market.curve.CurveNode; 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.measure.Measures; import com.opengamma.strata.measure.fra.FraTradeCalculationFunction; import com.opengamma.strata.measure.rate.RatesMarketDataLookup; import com.opengamma.strata.measure.swap.SwapTradeCalculationFunction; import com.opengamma.strata.product.Trade; import com.opengamma.strata.product.fra.FraTrade; import com.opengamma.strata.product.swap.SwapTrade; /** * Test curves. */ @Test public class CurveEndToEndTest { /** 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(); /** * End-to-end test for curve calibration and round-tripping that uses the {@link MarketDataFactory} * to calibrate a curve and calculate PVs for the instruments at the curve nodes. * * This tests the full pipeline of market data functions: * - Par rates * - Curve group (including calibration) * - Individual curves * - Discount factors */ public void roundTripFraAndFixedFloatSwap() { // Configuration and market data for the curve --------------------------------- String fra3x6 = "fra3x6"; String fra6x9 = "fra6x9"; String swap1y = "swap1y"; String swap2y = "swap2y"; String swap3y = "swap3y"; FraCurveNode fra3x6Node = fraNode(3, fra3x6); FraCurveNode fra6x9Node = fraNode(6, fra6x9); FixedIborSwapCurveNode swap1yNode = fixedIborSwapNode(Tenor.TENOR_1Y, swap1y); FixedIborSwapCurveNode swap2yNode = fixedIborSwapNode(Tenor.TENOR_2Y, swap2y); FixedIborSwapCurveNode swap3yNode = fixedIborSwapNode(Tenor.TENOR_3Y, swap3y); Map<ObservableId, Double> parRateData = ImmutableMap.<ObservableId, Double>builder() .put(id(fra3x6), 0.0037) .put(id(fra6x9), 0.0054) .put(id(swap1y), 0.005) .put(id(swap2y), 0.0087) .put(id(swap3y), 0.012) .build(); LocalDate valuationDate = date(2011, 3, 8); // Build the trades from the node instruments MarketData quotes = ImmutableMarketData.of(valuationDate, parRateData); Trade fra3x6Trade = fra3x6Node.trade(1d, quotes, REF_DATA); Trade fra6x9Trade = fra6x9Node.trade(1d, quotes, REF_DATA); Trade swap1yTrade = swap1yNode.trade(1d, quotes, REF_DATA); Trade swap2yTrade = swap2yNode.trade(1d, quotes, REF_DATA); Trade swap3yTrade = swap3yNode.trade(1d, quotes, REF_DATA); List<Trade> trades = ImmutableList.of(fra3x6Trade, fra6x9Trade, swap1yTrade, swap2yTrade, swap3yTrade); List<CurveNode> nodes = ImmutableList.of(fra3x6Node, fra6x9Node, swap1yNode, swap2yNode, swap3yNode); CurveGroupName groupName = CurveGroupName.of("Curve Group"); CurveName curveName = CurveName.of("FRA and Fixed-Float Swap Curve"); InterpolatedNodalCurveDefinition curveDefn = InterpolatedNodalCurveDefinition.builder() .name(curveName) .xValueType(ValueType.YEAR_FRACTION) .yValueType(ValueType.ZERO_RATE) .dayCount(DayCounts.ACT_ACT_ISDA) .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(); // Rules for market data and calculations --------------------------------- RatesMarketDataLookup ratesLookup = RatesMarketDataLookup.of(groupDefn); CalculationRules calculationRules = CalculationRules.of(functions(), Currency.USD, ratesLookup); // Calculate the results and check the PVs for the node instruments are zero ---------------------- List<Column> columns = ImmutableList.of(Column.of(Measures.PRESENT_VALUE)); MarketData knownMarketData = MarketData.of(date(2011, 3, 8), parRateData); // using the direct executor means there is no need to close/shutdown the runner CalculationTasks tasks = CalculationTasks.of(calculationRules, trades, columns); MarketDataRequirements reqs = tasks.requirements(REF_DATA); MarketData enhancedMarketData = marketDataFactory().create(reqs, marketDataConfig, knownMarketData, REF_DATA); CalculationTaskRunner runner = CalculationTaskRunner.of(MoreExecutors.newDirectExecutorService()); Results results = runner.calculate(tasks, enhancedMarketData, REF_DATA); results.getCells().stream().forEach(this::checkPvIsZero); } private void checkPvIsZero(Result<?> result) { assertThat(result).isSuccess(); assertThat(((CurrencyAmount) result.getValue()).getAmount()).isEqualTo(0, offset(PV_TOLERANCE)); } //----------------------------------------------------------------------------------------------------------- private static CalculationFunctions functions() { return CalculationFunctions.of(ImmutableMap.of( SwapTrade.class, new SwapTradeCalculationFunction(), FraTrade.class, new FraTradeCalculationFunction())); } }