/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame.swaption; 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 com.opengamma.util.money.Currency.USD; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.closeTo; import static org.hamcrest.Matchers.is; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import org.threeten.bp.Instant; import org.threeten.bp.LocalDate; import org.threeten.bp.Period; import org.threeten.bp.ZoneOffset; import org.threeten.bp.ZonedDateTime; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.opengamma.analytics.financial.interestrate.PresentValueSABRSensitivityDataBundle; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; import com.opengamma.analytics.util.amount.ReferenceAmount; import com.opengamma.core.id.ExternalSchemes; import com.opengamma.core.link.ConfigLink; import com.opengamma.core.link.SecurityLink; import com.opengamma.financial.analytics.curve.ConfigDBCurveConstructionConfigurationSource; import com.opengamma.financial.analytics.curve.CurveConstructionConfigurationSource; import com.opengamma.financial.analytics.curve.exposure.ConfigDBInstrumentExposuresProvider; import com.opengamma.financial.analytics.curve.exposure.InstrumentExposuresProvider; import com.opengamma.financial.convention.businessday.BusinessDayConventions; import com.opengamma.financial.convention.daycount.DayCounts; import com.opengamma.financial.convention.frequency.PeriodFrequency; import com.opengamma.financial.security.FinancialSecurity; import com.opengamma.financial.security.irs.FixedInterestRateSwapLeg; import com.opengamma.financial.security.irs.FloatingInterestRateSwapLeg; import com.opengamma.financial.security.irs.InterestRateSwapNotional; import com.opengamma.financial.security.irs.InterestRateSwapSecurity; import com.opengamma.financial.security.irs.PayReceiveType; import com.opengamma.financial.security.irs.Rate; import com.opengamma.financial.security.option.ExerciseType; import com.opengamma.financial.security.option.SwaptionSecurity; import com.opengamma.financial.security.swap.FloatingRateType; 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.CurveDefinitionFn; import com.opengamma.sesame.CurveNodeConverterFn; import com.opengamma.sesame.CurveSpecificationFn; import com.opengamma.sesame.CurveSpecificationMarketDataFn; 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.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.graph.FunctionModel; import com.opengamma.sesame.interestrate.InterestRateMockSources; import com.opengamma.sesame.marketdata.DefaultHistoricalMarketDataFn; import com.opengamma.sesame.marketdata.DefaultMarketDataFn; import com.opengamma.sesame.marketdata.HistoricalMarketDataFn; import com.opengamma.sesame.marketdata.MarketDataEnvironment; import com.opengamma.sesame.marketdata.MarketDataFn; import com.opengamma.sesame.sabr.DefaultSabrParametersProviderFn; import com.opengamma.sesame.sabr.SabrParametersProviderFn; import com.opengamma.util.GUIDGenerator; 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; import com.opengamma.util.tuple.DoublesPair; import com.opengamma.util.tuple.ObjectsPair; import com.opengamma.util.tuple.Pair; import com.opengamma.util.tuple.Pairs; /** * Tests the swaption analytics functions with expected values taken * from SwaptionPhysicalFixedIborSABRMethodE2ETest in og-analytics. */ @Test(groups = TestGroup.UNIT) public class SwaptionFnTest { private static final double TOLERANCE_PV = 1.0E-3; private static final ZonedDateTime VALUATION_DATE = DateUtils.getUTCDate(2014, 1, 22); private static final Environment ENV; private SwaptionFn _swaptionFn; private SwaptionSecurity _swaptionSecurity = createSingleSwaption(); static { LocalDate marketDataDate = LocalDate.of(2014, 2, 18); MarketDataEnvironment marketDataEnvironment = InterestRateMockSources.createMarketDataEnvironment(marketDataDate); ENV = new SimpleEnvironment(VALUATION_DATE, marketDataEnvironment.toBundle()); } @BeforeClass public void setUp() { FunctionModelConfig config = config( arguments( function( MarketExposureSelector.class, argument("exposureFunctions", ConfigLink.resolved(InterestRateMockSources.mockExposureFunctions()))), function( RootFinderConfiguration.class, argument("rootFinderAbsoluteTolerance", 1e-12), argument("rootFinderRelativeTolerance", 1e-12), argument("rootFinderMaxIterations", 5000)), function( DefaultSabrParametersProviderFn.class, argument("configurationName", "TEST_SABR")), function( DefaultCurveNodeConverterFn.class, argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofYears(1)))), function( DefaultDiscountingMulticurveBundleFn.class, argument("impliedCurveNames", StringSet.of()))), implementations( SwaptionFn.class, SabrSwaptionFn.class, InstrumentExposuresProvider.class, ConfigDBInstrumentExposuresProvider.class, SwaptionCalculatorFactory.class, SabrSwaptionCalculatorFactory.class, SabrParametersProviderFn.class, DefaultSabrParametersProviderFn.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, CurveSpecificationFn.class, DefaultCurveSpecificationFn.class, CurveNodeConverterFn.class, DefaultCurveNodeConverterFn.class, CurveConstructionConfigurationSource.class, ConfigDBCurveConstructionConfigurationSource.class, FixingsFn.class, DefaultFixingsFn.class, HistoricalMarketDataFn.class, DefaultHistoricalMarketDataFn.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); _swaptionFn = FunctionModel.build(SwaptionFn.class, config, ComponentMap.of(components)); } @Test public void testPresentValue() { Result<MultipleCurrencyAmount> pvComputed = _swaptionFn.calculatePV(ENV, _swaptionSecurity); assertThat(pvComputed.isSuccess(), is(true)); assertThat(pvComputed.getValue().getAmount(USD), is(closeTo(3156216.48957, TOLERANCE_PV))); } @Test public void testImpliedVolatility() { Result<Double> impliedVolComputed = _swaptionFn.calculateImpliedVolatility(ENV, _swaptionSecurity); assertThat(impliedVolComputed.isSuccess(), is(true)); assertThat(impliedVolComputed.getValue(), is(closeTo(0.298092262, 1E-8))); } @Test public void testPV01() { Result<ReferenceAmount<Pair<String, Currency>>> pv01Computed = _swaptionFn.calculatePV01(ENV, _swaptionSecurity); assertThat(pv01Computed.isSuccess(), is(true)); Map<Pair<String, Currency>, Double> results = pv01Computed.getValue().getMap(); assertThat(results.get(Pairs.of("USD-ON-OIS", USD)), is(closeTo(-2253.115361063714, 1E-8))); assertThat(results.get(Pairs.of("USD-LIBOR3M-FRAIRS", USD)), is(closeTo(32885.97222733803, 1E-8))); } @Test public void testBucketedPV01() { double[] deltaDsc = { -0.8970521909327039, -0.8970528138871251, 2.0679726864123788E-5, -2.800077859468568E-4, 0.020545355340195248, -28.660344224880443, 1.0311659235333974, -101.02574104758263, -162.90022502561072, -34.11856047817592, -41.87866271284144, -47.20852985708558, -52.64477419064427, -193.55488041593657, -379.8195117988651, 26.793804732157106, 259.3051035445537 }; double[] deltaFwd3 = { 0.6768377111533482, -0.013861472263779616, -0.00815248053117034, 28.045784074714817, -10296.86232676286, -9.445439010985615, -12.048126446934697, -60.09929275115254, 14090.425121330405, 28748.01823487962, 0.00, 0.00, 0.00, 0.00, 0.00 }; LinkedHashMap<Pair<String, Currency>, DoubleMatrix1D> sensitivity = new LinkedHashMap<>(); sensitivity.put(ObjectsPair.of("USD-ON-OIS", USD), new DoubleMatrix1D(deltaDsc)); sensitivity.put(ObjectsPair.of("USD-LIBOR3M-FRAIRS", USD), new DoubleMatrix1D(deltaFwd3)); MultipleCurrencyParameterSensitivity pvpsExpected = new MultipleCurrencyParameterSensitivity(sensitivity); Result<MultipleCurrencyParameterSensitivity> sensitivityResult = _swaptionFn.calculateBucketedPV01(ENV, _swaptionSecurity); assertThat(sensitivityResult.isSuccess(), is(true)); Map<Pair<String, Currency>, DoubleMatrix1D> sensitivities = sensitivityResult.getValue().getSensitivities(); Map<Pair<String, Currency>, DoubleMatrix1D> expected = pvpsExpected.getSensitivities(); assertThat(sensitivities.size(), is(expected.size())); assertThat(sensitivities.keySet(), is(expected.keySet())); for (Pair<String, Currency> key : sensitivities.keySet()) { double[] data = sensitivities.get(key).getData(); double[] expectedData = expected.get(key).getData(); assertThat(data.length, is(expectedData.length)); for (int i = 0; i < data.length; i++) { assertThat(data[i], is(closeTo(expectedData[i], 1E-4))); } } } @Test public void testBucketedSABRRisk() { Result<PresentValueSABRSensitivityDataBundle> result = _swaptionFn.calculateBucketedSABRRisk(ENV, _swaptionSecurity); assertThat(result.isSuccess(), is(true)); PresentValueSABRSensitivityDataBundle sabrRisk = result.getValue(); Map<DoublesPair, Double> alphaRiskExpected = ImmutableMap.of( DoublesPair.of(1.0, 5.0), 6204.475194599176, DoublesPair.of(2.0, 5.0), 3.946312129841228E7, DoublesPair.of(1.0, 10.0), 4136.961894403856, DoublesPair.of(2.0, 10.0), 2.6312850632053435E7); Map<DoublesPair, Double> alphaRiskComputed = sabrRisk.getAlpha().getMap(); checkSabrRiskValues(alphaRiskExpected, alphaRiskComputed); Map<DoublesPair, Double> betaRiskExpected = ImmutableMap.of( DoublesPair.of(1.0, 5.0), -1135.9264046809967, DoublesPair.of(2.0, 5.0), -7224978.7593665235, DoublesPair.of(1.0, 10.0), -757.402375482628, DoublesPair.of(2.0, 10.0), -4817403.709083163); Map<DoublesPair, Double> betaRiskComputed = sabrRisk.getBeta().getMap(); checkSabrRiskValues(betaRiskExpected, betaRiskComputed); Map<DoublesPair, Double> rhoRiskExpected = ImmutableMap.of( DoublesPair.of(1.0, 5.0), 25.108219123928023, DoublesPair.of(2.0, 5.0), 159699.0342933747, DoublesPair.of(1.0, 10.0), 16.74142332657722, DoublesPair.of(2.0, 10.0), 106482.62725264493); Map<DoublesPair, Double> rhoRiskComputed = sabrRisk.getRho().getMap(); checkSabrRiskValues(rhoRiskExpected, rhoRiskComputed); Map<DoublesPair, Double> nuRiskExpected = ImmutableMap.of( DoublesPair.of(1.0, 5.0), 37.75195237231597, DoublesPair.of(2.0, 5.0), 240118.59649586905, DoublesPair.of(1.0, 10.0), 25.17189343259352, DoublesPair.of(2.0, 10.0), 160104.0301854763); Map<DoublesPair, Double> nuRiskComputed = sabrRisk.getNu().getMap(); checkSabrRiskValues(nuRiskExpected, nuRiskComputed); } private void checkSabrRiskValues(Map<DoublesPair, Double> alphaRiskExpected, Map<DoublesPair, Double> alphaRiskComputed) { for (Map.Entry<DoublesPair, Double> entry : alphaRiskExpected.entrySet()) { assertThat(alphaRiskComputed.get(entry.getKey()), is(closeTo(entry.getValue(), 1E-4))); } } private SwaptionSecurity createSingleSwaption() { InterestRateSwapNotional notional = new InterestRateSwapNotional(Currency.USD, 100_000_000); PeriodFrequency freq6m = PeriodFrequency.of(Period.ofMonths(6)); Set<ExternalId> calendarUSNY = ImmutableSet.of(ExternalId.of(ExternalSchemes.ISDA_HOLIDAY, "USNY")); FixedInterestRateSwapLeg payLeg = new FixedInterestRateSwapLeg(); payLeg.setNotional(notional); payLeg.setDayCountConvention(DayCounts.THIRTY_U_360); payLeg.setPaymentDateFrequency(freq6m); payLeg.setPaymentDateBusinessDayConvention(BusinessDayConventions.MODIFIED_FOLLOWING); payLeg.setPaymentDateCalendars(calendarUSNY); payLeg.setAccrualPeriodFrequency(freq6m); payLeg.setAccrualPeriodBusinessDayConvention(BusinessDayConventions.MODIFIED_FOLLOWING); payLeg.setAccrualPeriodCalendars(calendarUSNY); payLeg.setRate(new Rate(0.035)); payLeg.setPayReceiveType(PayReceiveType.PAY); PeriodFrequency freq3m = PeriodFrequency.of(Period.ofMonths(3)); FloatingInterestRateSwapLeg receiveLeg = new FloatingInterestRateSwapLeg(); receiveLeg.setNotional(notional); receiveLeg.setDayCountConvention(DayCounts.ACT_360); receiveLeg.setPaymentDateFrequency(freq3m); receiveLeg.setPaymentDateBusinessDayConvention(BusinessDayConventions.MODIFIED_FOLLOWING); receiveLeg.setPaymentDateCalendars(calendarUSNY); receiveLeg.setPaymentOffset(0); receiveLeg.setAccrualPeriodFrequency(freq3m); receiveLeg.setAccrualPeriodBusinessDayConvention(BusinessDayConventions.MODIFIED_FOLLOWING); receiveLeg.setAccrualPeriodCalendars(calendarUSNY); receiveLeg.setResetPeriodFrequency(freq3m); receiveLeg.setResetPeriodBusinessDayConvention(BusinessDayConventions.MODIFIED_FOLLOWING); receiveLeg.setResetPeriodCalendars(calendarUSNY); receiveLeg.setFixingDateBusinessDayConvention(BusinessDayConventions.PRECEDING); receiveLeg.setFixingDateCalendars(calendarUSNY); receiveLeg.setFixingDateOffset(-2); receiveLeg.setFloatingRateType(FloatingRateType.IBOR); receiveLeg.setFloatingReferenceRateId(InterestRateMockSources.getLiborIndexId()); receiveLeg.setPayReceiveType(PayReceiveType.RECEIVE); //receiveLeg.setRollConvention(RollConvention.EOM); FinancialSecurity swap = new InterestRateSwapSecurity( ExternalIdBundle.of(ExternalId.of("UUID", GUIDGenerator.generate().toString())), "test swap", LocalDate.of(2016, 1, 26), // effective date LocalDate.of(2023, 1, 26), // maturity date, ImmutableSet.of(payLeg, receiveLeg)); SecurityLink<FinancialSecurity> swapLink = SecurityLink.resolved(swap); return new SwaptionSecurity(true, swapLink, true, new Expiry(LocalDate.of(2016, 1, 22).atStartOfDay(ZoneOffset.UTC)), false, USD, 100_000_000d, ExerciseType.of("European"), LocalDate.of(2016, 1, 26).atStartOfDay(ZoneOffset.UTC)); } }