/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate.payments.provider; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import org.threeten.bp.LocalDate; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.datasets.CalendarUSD; import com.opengamma.analytics.financial.instrument.NotionalProvider; import com.opengamma.analytics.financial.instrument.annuity.AdjustedDateParameters; import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition; import com.opengamma.analytics.financial.instrument.annuity.FloatingAnnuityDefinitionBuilder; import com.opengamma.analytics.financial.instrument.annuity.OffsetAdjustedDateParameters; import com.opengamma.analytics.financial.instrument.annuity.OffsetType; import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIbor; import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIborMaster; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.instrument.payment.CouponDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponIborFxResetDefinition; import com.opengamma.analytics.financial.instrument.swap.SwapDefinition; import com.opengamma.analytics.financial.interestrate.annuity.derivative.Annuity; import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIborFxReset; import com.opengamma.analytics.financial.interestrate.payments.derivative.Payment; import com.opengamma.analytics.financial.interestrate.swap.derivative.Swap; import com.opengamma.analytics.financial.provider.calculator.discounting.CurrencyExposureDiscountingCalculator; import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueCurveSensitivityDiscountingCalculator; import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator; import com.opengamma.analytics.financial.provider.description.MulticurveProviderDiscountDataSets; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.financial.provider.description.interestrate.ParameterProviderInterface; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity; import com.opengamma.analytics.financial.provider.sensitivity.multicurve.ParameterSensitivityMulticurveDiscountInterpolatedFDCalculator; import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator; import com.opengamma.analytics.financial.util.AssertSensitivityObjects; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.rolldate.RollConvention; import com.opengamma.util.money.Currency; import com.opengamma.util.money.MultipleCurrencyAmount; import com.opengamma.util.time.DateUtils; /** * Test the pricing method for coupon ibor with spread and FX reset notional. */ public class CouponIborFxResetDiscountingMethodTest { private static final MulticurveProviderDiscount MULTICURVE = MulticurveProviderDiscountDataSets.createMulticurveEurUsd(); private static final IborIndex[] IBOR_INDEXES = MulticurveProviderDiscountDataSets.getIndexesIborMulticurveEurUsd(); private static final IborIndex EURIBOR3M = IBOR_INDEXES[0]; private static final Calendar CALENDAR = MulticurveProviderDiscountDataSets.getEURCalendar(); private static final Currency CUR_REF = Currency.EUR; private static final Currency CUR_PAY = Currency.USD; private static final ZonedDateTime PAYMENT_DATE = DateUtils.getUTCDate(2011, 4, 7); private static final ZonedDateTime ACCRUAL_START_DATE = DateUtils.getUTCDate(2011, 1, 6); private static final ZonedDateTime ACCRUAL_END_DATE = DateUtils.getUTCDate(2011, 4, 6); private static final ZonedDateTime FX_FIXING_DATE = DateUtils.getUTCDate(2011, 1, 3); private static final ZonedDateTime FX_DELIVERY_DATE = DateUtils.getUTCDate(2011, 1, 6); private static final double ACCRUAL_FACTOR = 0.267; private static final double NOTIONAL = 1000000; //1m private static final double SPREAD = -0.001; // -10 bps private static final ZonedDateTime FIXING_DATE_SAME_AS_FX = DateUtils.getUTCDate(2011, 1, 3); private static final ZonedDateTime VALUATION_DATE = DateUtils.getUTCDate(2010, 12, 10); private static final CouponIborFxResetDefinition CPN_DFN = new CouponIborFxResetDefinition(CUR_PAY, PAYMENT_DATE, ACCRUAL_START_DATE, ACCRUAL_END_DATE, ACCRUAL_FACTOR, NOTIONAL, FIXING_DATE_SAME_AS_FX, EURIBOR3M, SPREAD, CALENDAR, CUR_REF, FX_FIXING_DATE, FX_DELIVERY_DATE); private static final CouponIborFxReset CPN = CPN_DFN.toDerivative(VALUATION_DATE); private static final CouponIborFxResetDiscountingMethod METHOD_CPN_IBOR_FX = CouponIborFxResetDiscountingMethod .getInstance(); private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance(); private static final CurrencyExposureDiscountingCalculator CEDC = CurrencyExposureDiscountingCalculator.getInstance(); private static final PresentValueCurveSensitivityDiscountingCalculator PVCSDC = PresentValueCurveSensitivityDiscountingCalculator.getInstance(); private static final ParameterSensitivityParameterCalculator<ParameterProviderInterface> PSC = new ParameterSensitivityParameterCalculator<>(PVCSDC); private static final double SHIFT = 1.0E-6; private static final ParameterSensitivityMulticurveDiscountInterpolatedFDCalculator PSC_DSC_FD = new ParameterSensitivityMulticurveDiscountInterpolatedFDCalculator(PVDC, SHIFT); private static final double TOLERANCE_PV = 1.0E-2; private static final double TOLERANCE_PV_DELTA = 1.0E+0; /** * Test Coupon Ibor PV */ @Test public void presentValue() { double fxToday = MULTICURVE.getFxRate(CUR_REF, CUR_PAY); double forward = MULTICURVE.getSimplyCompoundForwardRate(EURIBOR3M, CPN.getIborIndexFixingPeriodStartTime(), CPN.getIborIndexFixingPeriodEndTime(), CPN.getIborIndexFixingAccrualFactor()); double amount = fxToday * CPN.getNotional() * (forward + SPREAD) * CPN.getPaymentYearFraction(); double dfXTp = MULTICURVE.getDiscountFactor(CUR_PAY, CPN.getPaymentTime()); double dfYT0 = MULTICURVE.getDiscountFactor(CUR_REF, CPN.getFxDeliveryTime()); double dfXT0 = MULTICURVE.getDiscountFactor(CUR_PAY, CPN.getFxDeliveryTime()); double pvExpected = amount * dfXTp * dfYT0 / dfXT0; MultipleCurrencyAmount pvComputed = METHOD_CPN_IBOR_FX.presentValue(CPN, MULTICURVE); assertTrue("CouponIborFxResetDiscountingMethod: present value", pvComputed.size() == 1); assertEquals("CouponIborFxResetDiscountingMethod: present value", pvExpected, pvComputed.getAmount(CUR_PAY), TOLERANCE_PV); } /** * Test Coupon Ibor currency exposure */ @Test public void currencyExposure() { MultipleCurrencyAmount pvComputed = METHOD_CPN_IBOR_FX.presentValue(CPN, MULTICURVE); MultipleCurrencyAmount ceComputed = METHOD_CPN_IBOR_FX.currencyExposure(CPN, MULTICURVE); assertEquals("CouponIborFxResetDiscountingMethod: present currencyExposure", MULTICURVE.getFxRates().convert(ceComputed, CUR_PAY).getAmount(), MULTICURVE.getFxRates().convert(pvComputed, CUR_PAY).getAmount(), TOLERANCE_PV); assertTrue("CouponIborFxResetDiscountingMethod: present currencyExposure", Math.abs(ceComputed.getAmount(CUR_REF)) > TOLERANCE_PV); double forward = MULTICURVE.getSimplyCompoundForwardRate(EURIBOR3M, CPN.getIborIndexFixingPeriodStartTime(), CPN.getIborIndexFixingPeriodEndTime(), CPN.getIborIndexFixingAccrualFactor()); double amount = CPN.getNotional() * (forward + SPREAD) * CPN.getPaymentYearFraction(); double dfXTp = MULTICURVE.getDiscountFactor(CUR_PAY, CPN.getPaymentTime()); double dfYT0 = MULTICURVE.getDiscountFactor(CUR_REF, CPN.getFxDeliveryTime()); double dfXT0 = MULTICURVE.getDiscountFactor(CUR_PAY, CPN.getFxDeliveryTime()); double ceExpected = amount * dfXTp * dfYT0 / dfXT0; assertEquals("CouponIborFxResetDiscountingMethod: present currencyExposure", ceExpected, ceComputed.getAmount(CUR_REF), TOLERANCE_PV); } /** * Currency exposure via Calculator */ @Test public void currencyExposureCalculatorVsMethod() { MultipleCurrencyAmount ceMethod = METHOD_CPN_IBOR_FX.currencyExposure(CPN, MULTICURVE); MultipleCurrencyAmount ceCalculator = CPN.accept(CEDC, MULTICURVE); assertEquals("CouponIborFxResetDiscountingMethod: present value", ceMethod.getAmount(CUR_REF), ceCalculator.getAmount(CUR_REF), TOLERANCE_PV); } /** * PV via Calculator */ @Test public void presentValueCalculatorVsMethod() { MultipleCurrencyAmount pvMethod = METHOD_CPN_IBOR_FX.presentValue(CPN, MULTICURVE); MultipleCurrencyAmount pvCalculator = CPN.accept(PVDC, MULTICURVE); assertEquals("CouponIborFxResetDiscountingMethod: present value", pvMethod.getAmount(CUR_PAY), pvCalculator.getAmount(CUR_PAY), TOLERANCE_PV); } /** * Test PV sensitivity against finite difference approximation */ @Test public void presentValueCurveSensitivity() { MultipleCurrencyParameterSensitivity senseCalc1 = PSC.calculateSensitivity(CPN, MULTICURVE); MultipleCurrencyParameterSensitivity senseFd1 = PSC_DSC_FD.calculateSensitivity(CPN, MULTICURVE); AssertSensitivityObjects.assertEquals("CouponIborFxResetDiscountingMethod: curve sensitivity", senseCalc1, senseFd1, TOLERANCE_PV_DELTA); } /** * Swap with FX reset. EUR P3M v USD FX reset P3M */ private static final Calendar CAL = new CalendarUSD("CAL"); private static final GeneratorSwapFixedIborMaster GENERATOR_IRS_MASTER = GeneratorSwapFixedIborMaster.getInstance(); private static final GeneratorSwapFixedIbor EUR1YEURIBOR3M = GENERATOR_IRS_MASTER.getGenerator(GeneratorSwapFixedIborMaster.EUR1YEURIBOR3M, CAL); private static final AdjustedDateParameters ADJUSTED_DATE_IBOR = new AdjustedDateParameters(CAL, EUR1YEURIBOR3M.getBusinessDayConvention()); private static final OffsetAdjustedDateParameters OFFSET_ADJ_IBOR = new OffsetAdjustedDateParameters(-2, OffsetType.BUSINESS, CAL, EUR1YEURIBOR3M.getBusinessDayConvention()); private static final IborIndex EUREURIBOR3M = EUR1YEURIBOR3M.getIborIndex(); private static final LocalDate EFFECTIVE_DATE_1 = LocalDate.of(2016, 7, 18); private static final LocalDate MATURITY_DATE_1 = LocalDate.of(2017, 7, 18); private static final boolean PAYER_1 = false; private static final double NOTIONAL_1 = 1000000; // 1m private static final NotionalProvider NOTIONAL_PROV_1 = new NotionalProvider() { @Override public double getAmount(final LocalDate date) { return NOTIONAL_1; } }; private static final GeneratorSwapFixedIbor USD6MLIBOR6M = GENERATOR_IRS_MASTER.getGenerator(GeneratorSwapFixedIborMaster.USD6MLIBOR6M, CAL); private static final IborIndex USDLIBOR6M = USD6MLIBOR6M.getIborIndex(); // EUR Ibor leg EUR with exchange notional private static final AnnuityDefinition<? extends CouponDefinition> EUR_LEG_1_DEFINITION; static { EUR_LEG_1_DEFINITION = (AnnuityDefinition<? extends CouponDefinition>) new FloatingAnnuityDefinitionBuilder().payer(PAYER_1).notional(NOTIONAL_PROV_1).startDate(EFFECTIVE_DATE_1). endDate(MATURITY_DATE_1).index(EUREURIBOR3M).accrualPeriodFrequency(EUREURIBOR3M.getTenor()). rollDateAdjuster(RollConvention.NONE.getRollDateAdjuster(0)). resetDateAdjustmentParameters(ADJUSTED_DATE_IBOR).accrualPeriodParameters(ADJUSTED_DATE_IBOR). dayCount(EUREURIBOR3M.getDayCount()).fixingDateAdjustmentParameters(OFFSET_ADJ_IBOR). currency(EUREURIBOR3M.getCurrency()).exchangeInitialNotional(true).exchangeFinalNotional(true). startDateAdjustmentParameters(ADJUSTED_DATE_IBOR).endDateAdjustmentParameters(ADJUSTED_DATE_IBOR).build(); } // USD Ibor Leg with FX reset, spread with exchange notional private static final AnnuityDefinition<? extends CouponDefinition> USD_LEG_1_DEFINITION; static { AnnuityDefinition<? extends CouponDefinition> usdBase = (AnnuityDefinition<? extends CouponDefinition>) new FloatingAnnuityDefinitionBuilder().payer(PAYER_1).notional(NOTIONAL_PROV_1).startDate(EFFECTIVE_DATE_1). endDate(MATURITY_DATE_1).index(USDLIBOR6M).accrualPeriodFrequency(USDLIBOR6M.getTenor()). rollDateAdjuster(RollConvention.NONE.getRollDateAdjuster(0)) .resetDateAdjustmentParameters(ADJUSTED_DATE_IBOR).accrualPeriodParameters(ADJUSTED_DATE_IBOR). dayCount(USDLIBOR6M.getDayCount()).fixingDateAdjustmentParameters(OFFSET_ADJ_IBOR). currency(USDLIBOR6M.getCurrency()).exchangeInitialNotional(true) .exchangeFinalNotional(true).startDateAdjustmentParameters(ADJUSTED_DATE_IBOR) .endDateAdjustmentParameters(ADJUSTED_DATE_IBOR).build(); double sign = PAYER_1 ? 1.0d : -1.0d; int nbC = usdBase.getNumberOfPayments() - 2; // Remove notional CouponDefinition[] cpnFxReset = new CouponDefinition[nbC]; for (int loopcpn = 0; loopcpn < nbC; loopcpn++) { CouponIborDefinition cpnLoop = (CouponIborDefinition) usdBase.getNthPayment(loopcpn + 1); cpnFxReset[loopcpn] = new CouponIborFxResetDefinition(CUR_PAY, cpnLoop.getAccrualStartDate(), cpnLoop.getAccrualStartDate(), cpnLoop.getAccrualEndDate(), cpnLoop.getPaymentYearFraction(), sign * NOTIONAL_1, cpnLoop.getFixingDate(), USDLIBOR6M, SPREAD, CAL, CUR_REF, cpnLoop.getFixingDate().minusDays( 1), cpnLoop.getAccrualStartDate().minusDays(1)); } USD_LEG_1_DEFINITION = new AnnuityDefinition<>(cpnFxReset, CAL); } private static final SwapDefinition SWAP_1_DEFINITION = new SwapDefinition(EUR_LEG_1_DEFINITION, USD_LEG_1_DEFINITION); private static final Swap<? extends Payment, ? extends Payment> SWAP_1 = SWAP_1_DEFINITION .toDerivative(VALUATION_DATE); /** * Swap present value */ @Test public void presentValueSwap() { MultipleCurrencyAmount pvComputed = SWAP_1.accept(PVDC, MULTICURVE); MultipleCurrencyAmount pvExpected = SWAP_1.getFirstLeg().accept(PVDC, MULTICURVE); Annuity<? extends Payment> legFxRest = SWAP_1.getSecondLeg(); int nbCpn = legFxRest.getNumberOfPayments(); for (int loopcpn = 0; loopcpn < nbCpn; loopcpn++) { pvExpected = pvExpected.plus(legFxRest.getNthPayment(loopcpn).accept(PVDC, MULTICURVE)); } assertEquals("CouponFixedFxResetDiscountingMethod: present value", pvExpected.getAmount(CUR_PAY), pvComputed.getAmount(CUR_PAY), TOLERANCE_PV); } /** * Swap currency exposure */ @Test public void currencyExposureSwap() { MultipleCurrencyAmount ceComputed = SWAP_1.accept(CEDC, MULTICURVE); MultipleCurrencyAmount ceExpected = SWAP_1.getFirstLeg().accept(PVDC, MULTICURVE); Annuity<? extends Payment> legFxRest = SWAP_1.getSecondLeg(); int nbCpn = legFxRest.getNumberOfPayments(); for (int loopcpn = 0; loopcpn < nbCpn; loopcpn++) { ceExpected = ceExpected.plus(legFxRest.getNthPayment(loopcpn).accept(CEDC, MULTICURVE)); } assertEquals("CouponFixedFxResetDiscountingMethod: present value", ceExpected.getAmount(CUR_REF), ceComputed.getAmount(CUR_REF), TOLERANCE_PV); } }