/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame; 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.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.springframework.test.util.AssertionErrors.fail; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.threeten.bp.Period; import org.threeten.bp.ZoneId; import org.threeten.bp.ZonedDateTime; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.opengamma.core.config.ConfigSource; import com.opengamma.core.config.impl.ConfigItem; import com.opengamma.core.convention.ConventionSource; import com.opengamma.core.holiday.HolidaySource; import com.opengamma.core.holiday.impl.WeekendHolidaySource; import com.opengamma.core.legalentity.LegalEntitySource; import com.opengamma.core.region.RegionSource; import com.opengamma.core.region.impl.SimpleRegion; import com.opengamma.core.security.SecuritySource; import com.opengamma.financial.analytics.curve.CurveConstructionConfiguration; import com.opengamma.financial.analytics.curve.CurveGroupConfiguration; import com.opengamma.financial.analytics.curve.CurveNodeIdMapper; import com.opengamma.financial.analytics.curve.CurveTypeConfiguration; import com.opengamma.financial.analytics.curve.DiscountingCurveTypeConfiguration; import com.opengamma.financial.analytics.curve.InterpolatedCurveDefinition; import com.opengamma.financial.analytics.curve.OvernightCurveTypeConfiguration; import com.opengamma.financial.analytics.ircurve.CurveInstrumentProvider; import com.opengamma.financial.analytics.ircurve.StaticCurveInstrumentProvider; import com.opengamma.financial.analytics.ircurve.StaticCurvePointsInstrumentProvider; import com.opengamma.financial.analytics.ircurve.strips.CurveNode; import com.opengamma.financial.analytics.ircurve.strips.DataFieldType; import com.opengamma.financial.analytics.ircurve.strips.FXSwapNode; import com.opengamma.financial.analytics.ircurve.strips.SwapNode; import com.opengamma.financial.convention.ConventionBundleSource; import com.opengamma.financial.convention.FXForwardAndSwapConvention; import com.opengamma.financial.convention.FXSpotConvention; import com.opengamma.financial.convention.FinancialConvention; import com.opengamma.financial.convention.OISLegConvention; import com.opengamma.financial.convention.OvernightIndexConvention; import com.opengamma.financial.convention.StubType; import com.opengamma.financial.convention.SwapFixedLegConvention; import com.opengamma.financial.convention.businessday.BusinessDayConventions; import com.opengamma.financial.convention.daycount.DayCounts; import com.opengamma.financial.currency.CurrencyMatrix; import com.opengamma.financial.currency.SimpleCurrencyMatrix; import com.opengamma.financial.security.index.OvernightIndex; import com.opengamma.id.ExternalId; import com.opengamma.id.VersionCorrection; import com.opengamma.master.config.ConfigDocument; import com.opengamma.master.config.ConfigMaster; import com.opengamma.master.config.impl.InMemoryConfigMaster; import com.opengamma.master.config.impl.MasterConfigSource; import com.opengamma.master.convention.ConventionDocument; import com.opengamma.master.convention.ConventionMaster; import com.opengamma.master.convention.impl.InMemoryConventionMaster; import com.opengamma.master.convention.impl.MasterConventionSource; import com.opengamma.master.security.ManageableSecurity; import com.opengamma.master.security.SecurityDocument; import com.opengamma.master.security.SecurityMaster; import com.opengamma.master.security.impl.InMemorySecurityMaster; import com.opengamma.master.security.impl.MasterSecuritySource; import com.opengamma.service.ServiceContext; import com.opengamma.service.ThreadLocalServiceContext; import com.opengamma.service.VersionCorrectionProvider; 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.graph.FunctionModel; import com.opengamma.sesame.marketdata.DefaultMarketDataFn; import com.opengamma.sesame.marketdata.FxRateId; import com.opengamma.sesame.marketdata.HistoricalMarketDataFn; import com.opengamma.sesame.marketdata.MapMarketDataBundle; import com.opengamma.sesame.marketdata.MarketDataBundle; import com.opengamma.sesame.marketdata.MarketDataEnvironmentBuilder; import com.opengamma.sesame.marketdata.MarketDataFn; import com.opengamma.sesame.marketdata.RawId; import com.opengamma.util.money.Currency; import com.opengamma.util.result.Result; import com.opengamma.util.test.TestGroup; import com.opengamma.util.time.Tenor; /** * Tests that swap nodes can be used in curve construction. */ @Test(groups = TestGroup.UNIT) public class SwapNodeTest { private CurveConstructionConfiguration _ccc; private DiscountingMulticurveBundleResolverFn _curveFunction; private Environment _env; private final ConventionMaster _conventionMaster = new InMemoryConventionMaster(); private final ConfigMaster _configMaster = new InMemoryConfigMaster(); private final SecurityMaster _securityMaster = new InMemorySecurityMaster(); @BeforeMethod public void setup() { Map<ExternalId, Double> swapMarketDataSource = buildDataForFxSwapCurve(); Map<ExternalId, Double> usdMarketDataSource = buildDataForUsdCurve(); Double spotRate = swapMarketDataSource.get(ExternalId.of("TICKER", "SPOT")); ZonedDateTime valuationDate = ZonedDateTime.of(2014, 1, 10, 11, 0, 0, 0, ZoneId.of("America/Chicago")); MarketDataEnvironmentBuilder marketDataBuilder = new MarketDataEnvironmentBuilder(); for (Map.Entry<ExternalId, Double> entry : swapMarketDataSource.entrySet()) { marketDataBuilder.add(RawId.of(entry.getKey().toBundle()), entry.getValue()); } for (Map.Entry<ExternalId, Double> entry : usdMarketDataSource.entrySet()) { marketDataBuilder.add(RawId.of(entry.getKey().toBundle()), entry.getValue()); } marketDataBuilder.add(FxRateId.of(Currency.EUR, Currency.USD), spotRate); marketDataBuilder.valuationTime(valuationDate); MarketDataBundle marketDataBundle = new MapMarketDataBundle(marketDataBuilder.build()); List<? extends CurveTypeConfiguration> eurCurveTypes = ImmutableList.of( new DiscountingCurveTypeConfiguration("EUR")); List<? extends CurveTypeConfiguration> usdCurveTypes = ImmutableList.of( new DiscountingCurveTypeConfiguration("USD"), new OvernightCurveTypeConfiguration(ExternalId.of("BLOOMBERG_TICKER", "FEDL01 Index"))); Map<String, List<? extends CurveTypeConfiguration>> curves = new HashMap<>(); curves.put("FX Swap Curve", eurCurveTypes); curves.put("USD Discounting", usdCurveTypes); CurveGroupConfiguration groupConfiguration = new CurveGroupConfiguration(0, curves); _ccc = new CurveConstructionConfiguration( "FX Swap Curve CCC", ImmutableList.of(groupConfiguration), ImmutableList.<String>of()); RootFinderConfiguration rootFinderConfig = new RootFinderConfiguration(1e-9, 1e-9, 1000); SimpleCurrencyMatrix matrix = new SimpleCurrencyMatrix(); matrix.setFixedConversion(Currency.USD, Currency.EUR, 1.6584); RegionSource regionSource = mock(RegionSource.class); SimpleRegion region = new SimpleRegion(); region.setCurrency(Currency.USD); when(regionSource.getHighestLevelRegion(ExternalId.of("FINANCIAL_REGION", "US"))).thenReturn(region); ImmutableMap.Builder<Class<?>, Object> builder = ImmutableMap.<Class<?>, Object>builder() .put(ConventionSource.class, new MasterConventionSource(_conventionMaster)) .put(ConfigSource.class, new MasterConfigSource(_configMaster)) .put(HolidaySource.class, new WeekendHolidaySource()) .put(RegionSource.class, regionSource) .put(HistoricalMarketDataFn.class, mock(HistoricalMarketDataFn.class)) .put(LegalEntitySource.class, mock(LegalEntitySource.class)) .put(ConventionBundleSource.class, mock(ConventionBundleSource.class)) .put(CurrencyMatrix.class, matrix) .put(SecuritySource.class, new MasterSecuritySource(_securityMaster)); ComponentMap components = ComponentMap.of(builder.build()); FunctionModelConfig config = config( arguments( function( DefaultDiscountingMulticurveBundleFn.class, argument("rootFinderConfiguration", rootFinderConfig), argument("impliedCurveNames", StringSet.of())), function( DefaultCurveNodeConverterFn.class, argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofDays(1))))), implementations( CurveDefinitionFn.class, DefaultCurveDefinitionFn.class, CurveSpecificationFn.class, DefaultCurveSpecificationFn.class, CurveSpecificationMarketDataFn.class, DefaultCurveSpecificationMarketDataFn.class, CurveNodeConverterFn.class, DefaultCurveNodeConverterFn.class, FXMatrixFn.class, DefaultFXMatrixFn.class, DiscountingMulticurveBundleFn.class, DefaultDiscountingMulticurveBundleFn.class, DiscountingMulticurveBundleResolverFn.class, DefaultDiscountingMulticurveBundleResolverFn.class, MarketDataFn.class, DefaultMarketDataFn.class)); _curveFunction = FunctionModel.build(DiscountingMulticurveBundleResolverFn.class, config, components); _env = new SimpleEnvironment(valuationDate, marketDataBundle); VersionCorrectionProvider vcProvider = new VersionCorrectionProvider() { @Override public VersionCorrection getPortfolioVersionCorrection() { return VersionCorrection.LATEST; } @Override public VersionCorrection getConfigVersionCorrection() { return VersionCorrection.LATEST; } }; ThreadLocalServiceContext.init(ServiceContext.of(components.getComponents()) .with(VersionCorrectionProvider.class, vcProvider)); } @Test public void curveBuildsSuccessfully() { Result<MulticurveBundle> result = _curveFunction.generateBundle(_env, _ccc); if (!result.isSuccess()) { fail(failureMessage(result)); } } private Map<ExternalId, Double> buildDataForUsdCurve() { ExternalId payLegConvention = ExternalId.of("CONVENTION", "USD OIS Fixed Leg"); ExternalId receiveLegConvention = ExternalId.of("CONVENTION", "USD OIS Overnight Leg"); ExternalId onConventionId = ExternalId.of("BLOOMBERG_TICKER", "FEDL01 Index"); addConvention(new SwapFixedLegConvention( "USD OIS Fixed Leg", payLegConvention.toBundle(), Tenor.ONE_YEAR, DayCounts.ACT_360, BusinessDayConventions.MODIFIED_FOLLOWING, Currency.USD, ExternalId.of("FINANCIAL_REGION", "US"), 2, false, StubType.SHORT_START, false, 2)); addConvention(new OISLegConvention( "USD OIS Overnight Leg", receiveLegConvention.toBundle(), onConventionId, Tenor.ONE_YEAR, BusinessDayConventions.MODIFIED_FOLLOWING, 2, false, StubType.NONE, false, 2)); addSecurity(new OvernightIndex("USD OIS Overnight Leg", "description", onConventionId, onConventionId.toBundle())); addConvention(new OvernightIndexConvention( "FEDL01 Index", onConventionId.toBundle(), DayCounts.ACT_360, 1, Currency.USD, ExternalId.of("FINANCIAL_REGION", "US") )); String mapperName = "USD Discounting Mapper"; Set<CurveNode> nodes = ImmutableSet.<CurveNode>of( new SwapNode(Tenor.ofDays(0), Tenor.ONE_MONTH, payLegConvention, receiveLegConvention, mapperName), new SwapNode(Tenor.ofDays(0), Tenor.THREE_MONTHS, payLegConvention, receiveLegConvention, mapperName), new SwapNode(Tenor.ofDays(0), Tenor.SIX_MONTHS, payLegConvention, receiveLegConvention, mapperName), new SwapNode(Tenor.ofDays(0), Tenor.NINE_MONTHS, payLegConvention, receiveLegConvention, mapperName), new SwapNode(Tenor.ofDays(0), Tenor.ONE_YEAR, payLegConvention, receiveLegConvention, mapperName), new SwapNode(Tenor.ofDays(0), Tenor.TWO_YEARS, payLegConvention, receiveLegConvention, mapperName)); addConfig("USD Discounting", new InterpolatedCurveDefinition("USD Discounting", nodes, "Linear", "FlatExtrapolator", "FlatExtrapolator")); Map<Tenor, CurveInstrumentProvider> swapNodeIds = ImmutableMap.<Tenor, CurveInstrumentProvider>builder() .put(Tenor.ONE_MONTH, createProviderForSwap(Tenor.ONE_MONTH)) .put(Tenor.THREE_MONTHS, createProviderForSwap(Tenor.THREE_MONTHS)) .put(Tenor.SIX_MONTHS, createProviderForSwap(Tenor.SIX_MONTHS)) .put(Tenor.NINE_MONTHS, createProviderForSwap(Tenor.NINE_MONTHS)) .put(Tenor.ONE_YEAR, createProviderForSwap(Tenor.ONE_YEAR)) .put(Tenor.TWO_YEARS, createProviderForSwap(Tenor.TWO_YEARS)) .build(); addConfig(mapperName, CurveNodeIdMapper.builder() .name(mapperName) .swapNodeIds(swapNodeIds) .build()); return ImmutableMap.<ExternalId, Double>builder() .put(ExternalId.of("TICKER", "SWP_P1M"), 0.0008) .put(ExternalId.of("TICKER", "SWP_P3M"), 0.000875) .put(ExternalId.of("TICKER", "SWP_P6M"), 0.001) .put(ExternalId.of("TICKER", "SWP_P9M"), 0.00115) .put(ExternalId.of("TICKER", "SWP_P1Y"), 0.00129) .put(ExternalId.of("TICKER", "SWP_P2Y"), 0.0015) .build(); } private Map<ExternalId, Double> buildDataForFxSwapCurve() { ExternalId swapConventionId = ExternalId.of("CONVENTION", "FX Swap Convention"); ExternalId spotConventionId = ExternalId.of("CONVENTION", "FX Spot Convention"); addConvention(new FXForwardAndSwapConvention( "FX Swap Convention", swapConventionId.toBundle(), spotConventionId, BusinessDayConventions.FOLLOWING, false, ExternalId.of("FINANCIAL_REGION", "US"))); addConvention(new FXSpotConvention( "FX Spot Convention", spotConventionId.toBundle(), 2, ExternalId.of("FINANCIAL_REGION", "US"))); Set<CurveNode> nodes = ImmutableSet.of( createFxSwapNode(swapConventionId, Tenor.ONE_WEEK), createFxSwapNode(swapConventionId, Tenor.ONE_MONTH), createFxSwapNode(swapConventionId, Tenor.THREE_MONTHS), createFxSwapNode(swapConventionId, Tenor.SIX_MONTHS), createFxSwapNode(swapConventionId, Tenor.ONE_YEAR)); addConfig("FX Swap Curve", new InterpolatedCurveDefinition("FX Swap Curve", nodes, "DoubleQuadratic", "FlatExtrapolator")); Map<Tenor, CurveInstrumentProvider> fxSwapNodeIds = ImmutableMap.of( Tenor.ONE_WEEK, createProviderForFxSwap(Tenor.ONE_WEEK), Tenor.ONE_MONTH, createProviderForFxSwap(Tenor.ONE_MONTH), Tenor.THREE_MONTHS, createProviderForFxSwap(Tenor.THREE_MONTHS), Tenor.SIX_MONTHS, createProviderForFxSwap(Tenor.SIX_MONTHS), Tenor.ONE_YEAR, createProviderForFxSwap(Tenor.ONE_YEAR) ); addConfig("FX Swap Mapper", CurveNodeIdMapper.builder() .name("FX Swap Mapper") .fxSwapNodeIds(fxSwapNodeIds) .build()); return ImmutableMap.<ExternalId, Double>builder() .put(ExternalId.of("TICKER", "FXSWP_P7D"), -0.00082) .put(ExternalId.of("TICKER", "FXSWP_P1M"), -0.00085) .put(ExternalId.of("TICKER", "FXSWP_P3M"), -0.00093) .put(ExternalId.of("TICKER", "FXSWP_P6M"), -0.000925) .put(ExternalId.of("TICKER", "FXSWP_P1Y"), -0.00095) .put(ExternalId.of("TICKER", "SPOT"), 1.6584) .build(); } private void addConfig(String name, Object configObject) { _configMaster.add(new ConfigDocument(ConfigItem.of(configObject, name, configObject.getClass()))); } private void addSecurity(ManageableSecurity security) { _securityMaster.add(new SecurityDocument(security)); } private void addConvention(FinancialConvention convention) { _conventionMaster.add(new ConventionDocument(convention)); } private String failureMessage(Result<MulticurveBundle> result) { return result.getFailureMessage(); } private CurveInstrumentProvider createProviderForFxSwap(Tenor identifier) { return new StaticCurvePointsInstrumentProvider(ExternalId.of("TICKER", "FXSWP_" + identifier.getPeriod().toString()), "Market_Value", DataFieldType.POINTS, ExternalId.of("TICKER", "SPOT"), "Market_Value"); } private CurveInstrumentProvider createProviderForSwap(Tenor identifier) { return new StaticCurveInstrumentProvider(ExternalId.of("TICKER", "SWP_" + identifier.getPeriod().toString()), "Market_Value", DataFieldType.OUTRIGHT); } private CurveNode createFxSwapNode(ExternalId swapConventionId, Tenor maturityTenor) { return new FXSwapNode(Tenor.ofDays(0), maturityTenor, swapConventionId, Currency.EUR, Currency.USD, "FX Swap Mapper"); } }