package com.opengamma.sesame.marketdata; import static com.opengamma.sesame.config.ConfigBuilder.argument; import static com.opengamma.sesame.config.ConfigBuilder.arguments; import static com.opengamma.sesame.config.ConfigBuilder.config; import static com.opengamma.sesame.config.ConfigBuilder.function; import static com.opengamma.sesame.config.ConfigBuilder.implementations; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; import static org.testng.AssertJUnit.fail; import java.io.IOException; import java.math.BigDecimal; import java.util.TimeZone; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.threeten.bp.Clock; import org.threeten.bp.Instant; import org.threeten.bp.LocalDate; import org.threeten.bp.LocalTime; import org.threeten.bp.OffsetTime; import org.threeten.bp.Period; import org.threeten.bp.ZoneId; import org.threeten.bp.ZoneOffset; import org.threeten.bp.ZonedDateTime; import com.google.common.collect.ImmutableMap; import com.opengamma.core.id.ExternalSchemes; import com.opengamma.core.link.ConfigLink; import com.opengamma.core.position.Counterparty; import com.opengamma.core.position.impl.SimpleCounterparty; import com.opengamma.core.position.impl.SimpleTrade; import com.opengamma.financial.analytics.curve.CurveConstructionConfiguration; import com.opengamma.financial.security.future.FederalFundsFutureSecurity; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.service.ServiceContext; import com.opengamma.service.ThreadLocalServiceContext; import com.opengamma.service.VersionCorrectionProvider; import com.opengamma.sesame.CurrencyPairsFn; import com.opengamma.sesame.CurveDefinitionFn; import com.opengamma.sesame.CurveNodeConverterFn; import com.opengamma.sesame.CurveSpecificationFn; import com.opengamma.sesame.CurveSpecificationMarketDataFn; import com.opengamma.sesame.DefaultCurrencyPairsFn; import com.opengamma.sesame.DefaultCurveDefinitionFn; import com.opengamma.sesame.DefaultCurveNodeConverterFn; import com.opengamma.sesame.DefaultCurveSpecificationFn; import com.opengamma.sesame.DefaultCurveSpecificationMarketDataFn; import com.opengamma.sesame.DefaultDiscountingMulticurveBundleFn; import com.opengamma.sesame.DefaultDiscountingMulticurveBundleResolverFn; import com.opengamma.sesame.DefaultFXMatrixFn; import com.opengamma.sesame.DefaultFixingsFn; import com.opengamma.sesame.DiscountingMulticurveBundleFn; import com.opengamma.sesame.DiscountingMulticurveBundleResolverFn; import com.opengamma.sesame.DiscountingMulticurveCombinerFn; import com.opengamma.sesame.Environment; import com.opengamma.sesame.ExposureFunctionsDiscountingMulticurveCombinerFn; import com.opengamma.sesame.FXMatrixFn; import com.opengamma.sesame.FixingsFn; import com.opengamma.sesame.MarketExposureSelector; import com.opengamma.sesame.MulticurveBundle; import com.opengamma.sesame.RootFinderConfiguration; import com.opengamma.sesame.SimpleEnvironment; import com.opengamma.sesame.component.RetrievalPeriod; import com.opengamma.sesame.component.StringSet; import com.opengamma.sesame.config.FunctionModelConfig; import com.opengamma.sesame.engine.ComponentMap; import com.opengamma.sesame.engine.FixedInstantVersionCorrectionProvider; import com.opengamma.sesame.fedfundsfuture.DefaultFedFundsFutureFn; import com.opengamma.sesame.fedfundsfuture.FedFundsFutureCalculatorFactory; import com.opengamma.sesame.fedfundsfuture.FedFundsFutureDiscountingCalculatorFactory; import com.opengamma.sesame.fedfundsfuture.FedFundsFutureFn; import com.opengamma.sesame.graph.FunctionModel; import com.opengamma.sesame.interestrate.InterestRateMockSources; import com.opengamma.sesame.trade.FedFundsFutureTrade; import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries; import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries; import com.opengamma.util.OpenGammaClock; import com.opengamma.util.money.Currency; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.result.Result; import com.opengamma.util.test.TestGroup; import com.opengamma.util.time.DateUtils; import com.opengamma.util.time.Expiry; @Test(groups = TestGroup.UNIT) public class FedFundFutureCurveTest { private static final ZonedDateTime VALUATION_TIME = DateUtils.getUTCDate(2014, 4, 17); private static final String CURVE_CONSTRUCTION_CONFIGURATION_USD_FFF = "USD_ON-FFF"; private static final double TOLERANCE_PV = 1.0E-4; private static final double EXPECTED_PV = 0.0000; private static final int NB_TRADE = 3; private static final double[] EXPECTED_PRICE = new double[NB_TRADE];// Price used for curve calibration private static final LocalDate[] EXPIRY_DATE = new LocalDate[NB_TRADE]; static { EXPECTED_PRICE[0] = 0.9990; // Price used for curve calibration FFJ4 EXPECTED_PRICE[1] = 0.999075; // Price used for curve calibration FFK4 EXPECTED_PRICE[2] = 0.999025; // Price used for curve calibration FFM4 EXPIRY_DATE[0] = LocalDate.of(2014, 4, 30); EXPIRY_DATE[1] = LocalDate.of(2014, 5, 30); EXPIRY_DATE[2] = LocalDate.of(2014, 6, 30); } private static final String TRADING_EX = "CME"; private static final String SETTLE_EX = "CME"; private static final Currency CCY = Currency.USD; private static final double UNIT_AMOUNT = 5000000.0d/12.0d; private static final ExternalId FED_FUND_INDEX_ID = InterestRateMockSources.getOvernightIndexId(); private static final String CATEGORY = "Category"; private static final int NB_CONTRACTS = 20 ; // 100 m private static final BigDecimal TRADE_QUANTITY = BigDecimal.valueOf(NB_CONTRACTS); private static final Counterparty COUNTERPARTY = new SimpleCounterparty(ExternalId.of(Counterparty.DEFAULT_SCHEME, "COUNTERPARTY")); private static final LocalDate TRADE_DATE = VALUATION_TIME.toLocalDate(); private static final OffsetTime TRADE_TIME = OffsetTime.of(LocalTime.of(0, 0), ZoneOffset.UTC); private DiscountingMulticurveBundleResolverFn _curveBundle; private FedFundsFutureFn _fedFundsFutureFn; private Clock _defaultInstance; @BeforeClass public void setUpClass() throws IOException { // required for futures lookup to work. // BloombergFutureUtils.getMonthlyExpiryCodeForFutures(String, int, LocalDate) uses LocalDate.now() // to infer the future code so it's important that this remains static from the point of view of this // test. _defaultInstance = OpenGammaClock.getInstance(); OpenGammaClock.setInstance(Clock.fixed(VALUATION_TIME.toInstant(), ZoneId.of("UTC"))); FunctionModelConfig config = config( arguments( function( MarketExposureSelector.class, argument("exposureFunctions", ConfigLink.resolved(InterestRateMockSources.mockFFExposureFunctions()))), function( DefaultDiscountingMulticurveBundleFn.class, argument("impliedCurveNames", StringSet.of())), function( RootFinderConfiguration.class, argument("rootFinderAbsoluteTolerance", 1e-9), argument("rootFinderRelativeTolerance", 1e-9), argument("rootFinderMaxIterations", 1000)), function( DefaultCurveNodeConverterFn.class, argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofYears(1))))), implementations( FedFundsFutureFn.class, DefaultFedFundsFutureFn.class, FedFundsFutureCalculatorFactory.class, FedFundsFutureDiscountingCalculatorFactory.class, CurveNodeConverterFn.class, DefaultCurveNodeConverterFn.class, CurrencyPairsFn.class, DefaultCurrencyPairsFn.class, CurveSpecificationMarketDataFn.class, DefaultCurveSpecificationMarketDataFn.class, FXMatrixFn.class, DefaultFXMatrixFn.class, DiscountingMulticurveCombinerFn.class, ExposureFunctionsDiscountingMulticurveCombinerFn.class, CurveDefinitionFn.class, DefaultCurveDefinitionFn.class, DiscountingMulticurveBundleFn.class, DefaultDiscountingMulticurveBundleFn.class, DiscountingMulticurveBundleResolverFn.class, DefaultDiscountingMulticurveBundleResolverFn.class, HistoricalMarketDataFn.class, DefaultHistoricalMarketDataFn.class, CurveSpecificationFn.class, DefaultCurveSpecificationFn.class, FixingsFn.class, DefaultFixingsFn.class, MarketDataFn.class, DefaultMarketDataFn.class)); ImmutableMap<Class<?>, Object> components = InterestRateMockSources.generateBaseComponents(); VersionCorrectionProvider vcProvider = new FixedInstantVersionCorrectionProvider(Instant.now()); ServiceContext serviceContext = ServiceContext.of(components).with(VersionCorrectionProvider.class, vcProvider); ThreadLocalServiceContext.init(serviceContext); _curveBundle = FunctionModel.build(DiscountingMulticurveBundleResolverFn.class, config, ComponentMap.of(components)); _fedFundsFutureFn = FunctionModel.build(FedFundsFutureFn.class, config, ComponentMap.of(components)); } private static MarketDataBundle createMarketDataBundle() { LocalDate[] dateFixing = new LocalDate[]{ LocalDate.of(2014, 4, 1), LocalDate.of(2014, 4, 2), LocalDate.of(2014, 4, 3), LocalDate.of(2014, 4, 4), LocalDate.of(2014, 4, 7), LocalDate.of(2014, 4, 8), LocalDate.of(2014, 4, 9), LocalDate.of(2014, 4, 10), LocalDate.of(2014, 4, 11), LocalDate.of(2014, 4, 14), LocalDate.of(2014, 4, 15) }; double[] rateFixing = new double[]{ 0.0010, 0.0011, 0.0012, 0.0013, 0.0014, 0.0015, 0.0015, 0.0015, 0.0015, 0.0014, 0.0015 }; LocalDate valuationDate = DateUtils.getUTCDate(2014, 4, 17).toLocalDate(); LocalDateDoubleTimeSeries fixingSeries = ImmutableLocalDateDoubleTimeSeries.of(dateFixing, rateFixing); ExternalIdBundle fixingSeriesId = InterestRateMockSources.getOvernightIndexId().toBundle(); ImmutableLocalDateDoubleTimeSeries irFutureSeries = ImmutableLocalDateDoubleTimeSeries.of(valuationDate, 0.975); ExternalId irFutureId = ExternalSchemes.syntheticSecurityId("Test future"); MarketDataEnvironmentBuilder builder = InterestRateMockSources.createMarketDataEnvironment(valuationDate, false).toBuilder(); builder.add(RawId.of(irFutureId.toBundle()), irFutureSeries); builder.add(RawId.of(fixingSeriesId), fixingSeries); builder.add(RawId.of(fixingSeriesId), fixingSeries); return new MapMarketDataBundle(builder.build()); } /** * Build the curve with Fed Fund futures (including the current one). * Re-price futures trade with trade date the calibration date and trade price the calibration price and compare to 0. */ @Test public void buildCurve() { MarketDataBundle marketDataBundle = createMarketDataBundle(); Environment env = new SimpleEnvironment(VALUATION_TIME, marketDataBundle); Result<MulticurveBundle> pairProviderBlock = _curveBundle.generateBundle(env, ConfigLink.resolvable(CURVE_CONSTRUCTION_CONFIGURATION_USD_FFF, CurveConstructionConfiguration.class).resolve()); if (!pairProviderBlock.isSuccess()) { fail(pairProviderBlock.getFailureMessage()); } // Re-pricing FF futures trades FedFundsFutureTrade[] ffTrades = new FedFundsFutureTrade[NB_TRADE]; for(int i = 0; i < NB_TRADE; i++) { ffTrades[i] = createFFTrade(EXPIRY_DATE[i], EXPECTED_PRICE[i]); } for(int i = 0; i < NB_TRADE; i++) { Result<MultipleCurrencyAmount> resultPVJ4 = _fedFundsFutureFn.calculatePV(env, ffTrades[i]); if (resultPVJ4.isSuccess()) { MultipleCurrencyAmount mca = resultPVJ4.getValue(); assertThat("FedFundFutureCurve: node " + i, mca.getCurrencyAmount(Currency.USD).getAmount(), is(closeTo(EXPECTED_PV, TOLERANCE_PV))); } else { fail(resultPVJ4.getFailureMessage()); } } } private FedFundsFutureTrade createFFTrade(LocalDate expiryDate, double tradePrice) { Expiry expiry = new Expiry(ZonedDateTime.of(expiryDate, LocalTime.of(0, 0), ZoneOffset.UTC)); FederalFundsFutureSecurity fedFundsFuture = new FederalFundsFutureSecurity(expiry, TRADING_EX, SETTLE_EX, CCY, UNIT_AMOUNT, FED_FUND_INDEX_ID, CATEGORY); fedFundsFuture.setExternalIdBundle(ExternalSchemes.syntheticSecurityId("Test future").toBundle()); SimpleTrade trade = new SimpleTrade(fedFundsFuture, TRADE_QUANTITY, COUNTERPARTY, TRADE_DATE, TRADE_TIME); trade.setPremiumCurrency(Currency.USD); trade.setPremium(tradePrice); return new FedFundsFutureTrade(trade); } @AfterClass public void tearDown() { OpenGammaClock.setInstance(_defaultInstance); } }