/** * 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.CouponFixedFxResetDefinition; import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition; 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.CouponFixedFxReset; 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.MultipleCurrencyMulticurveSensitivity; 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.test.TestGroup; import com.opengamma.util.time.DateUtils; /** * Tests the pricing method for fixed coupon with FX reset notional. */ @Test(groups = TestGroup.UNIT) public class CouponFixedFxResetDiscountingMethodTest { private static final ZonedDateTime VALUATION_DATE = DateUtils.getUTCDate(2010, 1, 6); /** Single coupon. */ 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, 6); private static final ZonedDateTime ACCRUAL_START_DATE = DateUtils.getUTCDate(2011, 1, 5); private static final ZonedDateTime ACCRUAL_END_DATE = DateUtils.getUTCDate(2011, 4, 5); 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 = 100000000; //100m private static final double RATE = 0.04; private static final CouponFixedFxResetDefinition CPN_DEFINITION = new CouponFixedFxResetDefinition(CUR_PAY, PAYMENT_DATE, ACCRUAL_START_DATE, ACCRUAL_END_DATE, ACCRUAL_FACTOR, NOTIONAL, RATE, CUR_REF, FX_FIXING_DATE, FX_DELIVERY_DATE); private static final CouponFixedFxReset CPN = CPN_DEFINITION.toDerivative(VALUATION_DATE); private static final MulticurveProviderDiscount MULTICURVE = MulticurveProviderDiscountDataSets.createMulticurveEurUsd(); /** Methods and calculators. */ private static final CouponFixedFxResetDiscountingMethod METHOD_CPN_FIXED_FX = CouponFixedFxResetDiscountingMethod.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 public void presentValue() { double fxToday = MULTICURVE.getFxRate(CUR_REF, CUR_PAY); double amount = CPN.paymentAmount(fxToday); 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_FIXED_FX.presentValue(CPN, MULTICURVE); assertTrue("CouponFixedFxResetDiscountingMethod: present value", pvComputed.size() == 1); assertEquals("CouponFixedFxResetDiscountingMethod: present value", pvExpected, pvComputed.getAmount(CUR_PAY), TOLERANCE_PV); } @Test public void presentValueCalculatorVsMethod() { MultipleCurrencyAmount pvMethod = METHOD_CPN_FIXED_FX.presentValue(CPN, MULTICURVE); MultipleCurrencyAmount pvCalculator = CPN.accept(PVDC, MULTICURVE); assertEquals("CouponFixedFxResetDiscountingMethod: present value", pvMethod.getAmount(CUR_PAY), pvCalculator.getAmount(CUR_PAY), TOLERANCE_PV); } @Test public void currencyExposure() { MultipleCurrencyAmount pvComputed = METHOD_CPN_FIXED_FX.presentValue(CPN, MULTICURVE); MultipleCurrencyAmount ceComputed = METHOD_CPN_FIXED_FX.currencyExposure(CPN, MULTICURVE); assertEquals("CouponFixedFxResetDiscountingMethod: present currencyExposure", MULTICURVE.getFxRates().convert(ceComputed, CUR_PAY).getAmount(), MULTICURVE.getFxRates().convert(pvComputed, CUR_PAY).getAmount(), TOLERANCE_PV); assertTrue("CouponFixedFxResetDiscountingMethod: present currencyExposure", Math.abs(ceComputed.getAmount(CUR_REF)) > TOLERANCE_PV); double amount = NOTIONAL * ACCRUAL_FACTOR * RATE; 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("CouponFixedFxResetDiscountingMethod: present currencyExposure", ceExpected, ceComputed.getAmount(CUR_REF), TOLERANCE_PV); } @Test public void currencyExposureCalculatorVsMethod() { MultipleCurrencyAmount ceMethod = METHOD_CPN_FIXED_FX.currencyExposure(CPN, MULTICURVE); MultipleCurrencyAmount ceCalculator = CPN.accept(CEDC, MULTICURVE); assertEquals("CouponFixedFxResetDiscountingMethod: present value", ceMethod.getAmount(CUR_REF), ceCalculator.getAmount(CUR_REF), TOLERANCE_PV); } @Test public void presentValueCurveSensitivity() { MultipleCurrencyParameterSensitivity senseCalc1 = PSC.calculateSensitivity(CPN, MULTICURVE); MultipleCurrencyParameterSensitivity senseFd1 = PSC_DSC_FD.calculateSensitivity(CPN, MULTICURVE); AssertSensitivityObjects.assertEquals("CouponFixedFxResetDiscountingMethod: curve sensitivity", senseCalc1, senseFd1, TOLERANCE_PV_DELTA); } @Test public void presentValueCurveSensitivityCalculatorVsMethod() { MultipleCurrencyMulticurveSensitivity pvcsMethod = METHOD_CPN_FIXED_FX.presentValueCurveSensitivity(CPN, MULTICURVE).cleaned(); MultipleCurrencyMulticurveSensitivity pvcsCalculator = CPN.accept(PVCSDC, MULTICURVE).cleaned(); AssertSensitivityObjects.assertEquals("CouponFixedFxResetDiscountingMethod: curve sensitivity", pvcsMethod, pvcsCalculator, 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 double FIXED_RATE_1 = 0.0200; 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; } }; // Ibor leg EUR with exchange notional private static final AnnuityDefinition<? extends CouponDefinition> IBOR_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(); // Fixed Leg USD with FX reset EUR with exchange notional private static final AnnuityDefinition<? extends CouponDefinition> LEG_FXRESET_FIXED_1_DEFINITION; static { double sign = PAYER_1 ? 1.0d : -1.0d; int nbCpn1 = IBOR_LEG_1_DEFINITION.getNumberOfPayments() - 2; // Remove notional CouponDefinition[] cpnFxReset = new CouponDefinition[3 * nbCpn1]; for(int loopcpn = 0; loopcpn<nbCpn1; loopcpn++) { CouponIborDefinition cpnLoop = (CouponIborDefinition) IBOR_LEG_1_DEFINITION.getNthPayment(loopcpn+1); cpnFxReset[3 * loopcpn] = new CouponFixedFxResetDefinition(CUR_PAY, cpnLoop.getAccrualStartDate(), cpnLoop.getAccrualStartDate(), cpnLoop.getAccrualStartDate(), 1.0d, -sign * NOTIONAL_1, 1.0d, CUR_REF, cpnLoop.getFixingDate(), cpnLoop.getAccrualStartDate()); cpnFxReset[1 + 3 * loopcpn] = new CouponFixedFxResetDefinition(CUR_PAY, cpnLoop.getAccrualEndDate(), cpnLoop.getAccrualStartDate(), cpnLoop.getAccrualEndDate(), cpnLoop.getPaymentYearFraction(), sign * NOTIONAL_1, FIXED_RATE_1, CUR_REF, cpnLoop.getFixingDate(), cpnLoop.getAccrualStartDate()); cpnFxReset[2 + 3 * loopcpn] = new CouponFixedFxResetDefinition(CUR_PAY, cpnLoop.getAccrualEndDate(), cpnLoop.getAccrualEndDate(), cpnLoop.getAccrualEndDate(), 1.0d, sign * NOTIONAL_1, 1.0d, CUR_REF, cpnLoop.getFixingDate(), cpnLoop.getAccrualStartDate()); } LEG_FXRESET_FIXED_1_DEFINITION = new AnnuityDefinition<>(cpnFxReset, CAL); } private static final SwapDefinition SWAP_1_DEFINITION = new SwapDefinition(IBOR_LEG_1_DEFINITION, LEG_FXRESET_FIXED_1_DEFINITION); private static final Swap<? extends Payment, ? extends Payment> SWAP_1 = SWAP_1_DEFINITION.toDerivative(VALUATION_DATE); 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); } 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); } }