/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.fx; import static com.opengamma.strata.basics.date.HolidayCalendarIds.USNY; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.time.LocalDate; import org.testng.annotations.Test; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.currency.CurrencyPair; import com.opengamma.strata.basics.currency.FxMatrix; import com.opengamma.strata.basics.currency.FxRate; import com.opengamma.strata.basics.currency.MultiCurrencyAmount; import com.opengamma.strata.basics.date.DaysAdjustment; import com.opengamma.strata.basics.index.FxIndex; import com.opengamma.strata.basics.index.FxIndexObservation; import com.opengamma.strata.basics.index.ImmutableFxIndex; import com.opengamma.strata.market.param.CurrencyParameterSensitivities; import com.opengamma.strata.market.sensitivity.PointSensitivities; import com.opengamma.strata.pricer.rate.RatesProvider; import com.opengamma.strata.pricer.sensitivity.RatesFiniteDifferenceSensitivityCalculator; import com.opengamma.strata.product.fx.ResolvedFxNdf; import com.opengamma.strata.product.fx.ResolvedFxSingle; /** * Test {@link DiscountingFxNdfProductPricer}. */ @Test public class DiscountingFxNdfProductPricerTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); private static final FxMatrix FX_MATRIX = RatesProviderFxDataSets.fxMatrix(); private static final RatesProvider PROVIDER = RatesProviderFxDataSets.createProvider(); private static final Currency KRW = Currency.KRW; private static final Currency USD = Currency.USD; private static final LocalDate PAYMENT_DATE = RatesProviderFxDataSets.VAL_DATE_2014_01_22.plusWeeks(8); private static final LocalDate PAYMENT_DATE_PAST = RatesProviderFxDataSets.VAL_DATE_2014_01_22.minusDays(1); private static final double NOMINAL_USD = 100_000_000; private static final CurrencyAmount CURRENCY_NOTIONAL = CurrencyAmount.of(USD, NOMINAL_USD); private static final double FX_RATE = 1123.45; private static final CurrencyAmount CURRENCY_NOTIONAL_INVERSE = CurrencyAmount.of(KRW, NOMINAL_USD * FX_RATE); private static final FxIndex INDEX = ImmutableFxIndex.builder() .name("USD/KRW") .currencyPair(CurrencyPair.of(USD, KRW)) .fixingCalendar(USNY) .maturityDateOffset(DaysAdjustment.ofBusinessDays(2, USNY)) .build(); private static final LocalDate FIXING_DATE = INDEX.calculateFixingFromMaturity(PAYMENT_DATE, REF_DATA); private static final LocalDate FIXING_DATE_PAST = INDEX.calculateFixingFromMaturity(PAYMENT_DATE_PAST, REF_DATA); private static final ResolvedFxNdf NDF = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL) .agreedFxRate(FxRate.of(USD, KRW, FX_RATE)) .observation(FxIndexObservation.of(INDEX, FIXING_DATE, REF_DATA)) .paymentDate(PAYMENT_DATE) .build(); private static final ResolvedFxNdf NDF_INVERSE = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL_INVERSE) .agreedFxRate(FxRate.of(USD, KRW, FX_RATE)) .observation(FxIndexObservation.of(INDEX, FIXING_DATE, REF_DATA)) .paymentDate(PAYMENT_DATE) .build(); private static final DiscountingFxNdfProductPricer PRICER = DiscountingFxNdfProductPricer.DEFAULT; private static final double TOL = 1.0E-12; private static final double EPS_FD = 1E-7; private static final RatesFiniteDifferenceSensitivityCalculator CAL_FD = new RatesFiniteDifferenceSensitivityCalculator(EPS_FD); public void test_presentValue() { CurrencyAmount computed = PRICER.presentValue(NDF, PROVIDER); double dscUsd = PROVIDER.discountFactor(USD, NDF.getPaymentDate()); double dscKrw = PROVIDER.discountFactor(KRW, NDF.getPaymentDate()); double expected = NOMINAL_USD * (dscUsd - dscKrw * FX_RATE / PROVIDER.fxRate(CurrencyPair.of(USD, KRW))); assertEquals(computed.getCurrency(), USD); assertEquals(computed.getAmount(), expected, NOMINAL_USD * TOL); } public void test_presentValue_inverse() { CurrencyAmount computed = PRICER.presentValue(NDF_INVERSE, PROVIDER); double dscUsd = PROVIDER.discountFactor(USD, NDF_INVERSE.getPaymentDate()); double dscKrw = PROVIDER.discountFactor(KRW, NDF_INVERSE.getPaymentDate()); double expected = NOMINAL_USD * FX_RATE * (dscKrw - dscUsd * 1 / FX_RATE / PROVIDER.fxRate(CurrencyPair.of(KRW, USD))); assertEquals(computed.getCurrency(), KRW); assertEquals(computed.getAmount(), expected, NOMINAL_USD * FX_RATE * TOL); } public void test_presentValue_ended() { ResolvedFxNdf ndf = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL) .agreedFxRate(FxRate.of(USD, KRW, FX_RATE)) .observation(FxIndexObservation.of(INDEX, FIXING_DATE_PAST, REF_DATA)) .paymentDate(PAYMENT_DATE_PAST) .build(); CurrencyAmount computed = PRICER.presentValue(ndf, PROVIDER); assertEquals(computed.getAmount(), 0d); } public void test_forwardValue() { FxRate computed = PRICER.forwardFxRate(NDF, PROVIDER); ResolvedFxNdf ndfFwd = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL) .agreedFxRate(computed) .observation(FxIndexObservation.of(INDEX, FIXING_DATE, REF_DATA)) .paymentDate(PAYMENT_DATE) .build(); CurrencyAmount computedFwd = PRICER.presentValue(ndfFwd, PROVIDER); assertEquals(computedFwd.getAmount(), 0d, NOMINAL_USD * TOL); } public void test_presentValueSensitivity() { PointSensitivities point = PRICER.presentValueSensitivity(NDF, PROVIDER); CurrencyParameterSensitivities computed = PROVIDER.parameterSensitivity(point); CurrencyParameterSensitivities expected = CAL_FD.sensitivity(PROVIDER, (p) -> PRICER.presentValue(NDF, (p))); assertTrue(computed.equalWithTolerance(expected, NOMINAL_USD * EPS_FD)); } public void test_presentValueSensitivity_ended() { ResolvedFxNdf ndf = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL) .agreedFxRate(FxRate.of(USD, KRW, FX_RATE)) .observation(FxIndexObservation.of(INDEX, FIXING_DATE_PAST, REF_DATA)) .paymentDate(PAYMENT_DATE_PAST) .build(); PointSensitivities computed = PRICER.presentValueSensitivity(ndf, PROVIDER); assertEquals(computed, PointSensitivities.empty()); } //------------------------------------------------------------------------- public void test_currencyExposure() { CurrencyAmount pv = PRICER.presentValue(NDF, PROVIDER); MultiCurrencyAmount ce = PRICER.currencyExposure(NDF, PROVIDER); CurrencyAmount ceConverted = ce.convertedTo(pv.getCurrency(), PROVIDER); assertEquals(pv.getAmount(), ceConverted.getAmount(), NOMINAL_USD * TOL); } public void test_currencyExposure_ended() { ResolvedFxNdf ndf = ResolvedFxNdf.builder() .settlementCurrencyNotional(CURRENCY_NOTIONAL) .agreedFxRate(FxRate.of(USD, KRW, FX_RATE)) .observation(FxIndexObservation.of(INDEX, LocalDate.of(2011, 5, 2), REF_DATA)) .paymentDate(LocalDate.of(2011, 5, 4)) .build(); MultiCurrencyAmount computed = PRICER.currencyExposure(ndf, PROVIDER); assertEquals(computed.size(), 0); } public void test_currencyExposure_from_pt_sensitivity() { MultiCurrencyAmount ceDirect = PRICER.currencyExposure(NDF, PROVIDER); PointSensitivities pts = PRICER.presentValueSensitivity(NDF, PROVIDER); MultiCurrencyAmount cePts = PROVIDER.currencyExposure(pts); CurrencyAmount cePv = PRICER.presentValue(NDF, PROVIDER); MultiCurrencyAmount ceExpected = cePts.plus(cePv); assertEquals(ceDirect.getAmount(USD).getAmount(), ceExpected.getAmount(USD).getAmount(), NOMINAL_USD * TOL); assertEquals(ceDirect.getAmount(KRW).getAmount(), ceExpected.getAmount(KRW).getAmount(), NOMINAL_USD * TOL * FX_MATRIX.fxRate(USD, KRW)); } public void test_currencyExposure_from_pt_sensitivity_inverse() { MultiCurrencyAmount ceDirect = PRICER.currencyExposure(NDF_INVERSE, PROVIDER); PointSensitivities pts = PRICER.presentValueSensitivity(NDF_INVERSE, PROVIDER); MultiCurrencyAmount cePts = PROVIDER.currencyExposure(pts); CurrencyAmount cePv = PRICER.presentValue(NDF_INVERSE, PROVIDER); MultiCurrencyAmount ceExpected = cePts.plus(cePv); assertEquals(ceDirect.getAmount(USD).getAmount(), ceExpected.getAmount(USD).getAmount(), NOMINAL_USD * TOL); assertEquals(ceDirect.getAmount(KRW).getAmount(), ceExpected.getAmount(KRW).getAmount(), NOMINAL_USD * TOL * FX_MATRIX.fxRate(USD, KRW)); } //------------------------------------------------------------------------- private static final ResolvedFxSingle FOREX = ResolvedFxSingle.of(CurrencyAmount.of(USD, NOMINAL_USD), FxRate.of(USD, KRW, FX_RATE), PAYMENT_DATE); private static final DiscountingFxSingleProductPricer PRICER_FX = DiscountingFxSingleProductPricer.DEFAULT; // Checks that the NDF present value is coherent with the standard FX forward present value. public void test_presentValueVsForex() { CurrencyAmount pvNDF = PRICER.presentValue(NDF, PROVIDER); MultiCurrencyAmount pvFX = PRICER_FX.presentValue(FOREX, PROVIDER); assertEquals( pvNDF.getAmount(), pvFX.getAmount(USD).getAmount() + pvFX.getAmount(KRW).getAmount() * FX_MATRIX.fxRate(KRW, USD), NOMINAL_USD * TOL); } // Checks that the NDF currency exposure is coherent with the standard FX forward present value. public void test_currencyExposureVsForex() { MultiCurrencyAmount pvNDF = PRICER.currencyExposure(NDF, PROVIDER); MultiCurrencyAmount pvFX = PRICER_FX.currencyExposure(FOREX, PROVIDER); assertEquals(pvNDF.getAmount(USD).getAmount(), pvFX.getAmount(USD).getAmount(), NOMINAL_USD * TOL); assertEquals(pvNDF.getAmount(KRW).getAmount(), pvFX.getAmount(KRW).getAmount(), NOMINAL_USD * TOL * FX_MATRIX.fxRate(USD, KRW)); } // Checks that the NDF forward rate is coherent with the standard FX forward present value. public void test_forwardRateVsForex() { FxRate fwdNDF = PRICER.forwardFxRate(NDF, PROVIDER); FxRate fwdFX = PRICER_FX.forwardFxRate(FOREX, PROVIDER); assertEquals(fwdNDF.fxRate(fwdNDF.getPair()), fwdFX.fxRate(fwdFX.getPair()), 1e-10); } // Checks that the NDF present value sensitivity is coherent with the standard FX forward present value. public void test_presentValueCurveSensitivityVsForex() { PointSensitivities pvcsNDF = PRICER.presentValueSensitivity(NDF, PROVIDER).normalized(); CurrencyParameterSensitivities sensiNDF = PROVIDER.parameterSensitivity(pvcsNDF); PointSensitivities pvcsFX = PRICER_FX.presentValueSensitivity(FOREX, PROVIDER).normalized(); CurrencyParameterSensitivities sensiFX = PROVIDER.parameterSensitivity(pvcsFX); assertTrue(sensiNDF.equalWithTolerance(sensiFX.convertedTo(USD, PROVIDER), NOMINAL_USD * TOL)); } //------------------------------------------------------------------------- public void test_currentCash_zero() { CurrencyAmount computed = PRICER.currentCash(NDF, PROVIDER); assertEquals(computed, CurrencyAmount.zero(NDF.getSettlementCurrency())); } public void test_currentCash_onPayment() { double rate = 1111.2; LocalDate paymentDate = NDF.getPaymentDate(); RatesProvider provider = RatesProviderFxDataSets.createProvider(paymentDate, NDF.getIndex(), rate); CurrencyAmount computed = PRICER.currentCash(NDF, provider); assertEquals(computed, CurrencyAmount.of(NDF.getSettlementCurrency(), NOMINAL_USD * (1d - FX_RATE / rate))); } }