/**
* 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.currency.Currency.EUR;
import static com.opengamma.strata.basics.currency.Currency.GBP;
import static com.opengamma.strata.basics.currency.Currency.USD;
import static com.opengamma.strata.basics.date.DayCounts.ACT_365F;
import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg;
import static com.opengamma.strata.collect.TestHelper.coverBeanEquals;
import static com.opengamma.strata.collect.TestHelper.coverImmutableBean;
import static com.opengamma.strata.collect.TestHelper.date;
import static org.testng.Assert.assertEquals;
import java.time.LocalDate;
import java.util.Optional;
import org.testng.annotations.Test;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.currency.CurrencyAmount;
import com.opengamma.strata.basics.currency.CurrencyPair;
import com.opengamma.strata.basics.currency.FxRate;
import com.opengamma.strata.basics.currency.MultiCurrencyAmount;
import com.opengamma.strata.basics.date.DayCounts;
import com.opengamma.strata.basics.index.FxIndexObservation;
import com.opengamma.strata.basics.index.FxIndices;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.market.curve.CurveMetadata;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.Curves;
import com.opengamma.strata.market.curve.InterpolatedNodalCurve;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolators;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.DiscountFactors;
import com.opengamma.strata.pricer.ZeroRateDiscountFactors;
import com.opengamma.strata.pricer.impl.swap.DiscountingRatePaymentPeriodPricer;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;
import com.opengamma.strata.product.rate.FixedRateComputation;
import com.opengamma.strata.product.swap.FxReset;
import com.opengamma.strata.product.swap.RateAccrualPeriod;
import com.opengamma.strata.product.swap.RatePaymentPeriod;
/**
* Test {@link DiscountFxForwardRates}.
*/
@Test
public class DiscountFxForwardRatesTest {
private static final ReferenceData REF_DATA = ReferenceData.standard();
private static final LocalDate DATE_VAL = date(2015, 6, 4);
private static final LocalDate DATE_REF = date(2015, 7, 30);
private static final FxRate FX_RATE = FxRate.of(GBP, USD, 1.5d);
private static final CurrencyPair CURRENCY_PAIR = CurrencyPair.of(GBP, USD);
private static final CurveInterpolator INTERPOLATOR = CurveInterpolators.LINEAR;
private static final CurveMetadata METADATA1 = Curves.zeroRates("TestCurve", ACT_365F);
private static final CurveMetadata METADATA2 = Curves.zeroRates("TestCurveUSD", ACT_365F);
private static final InterpolatedNodalCurve CURVE1 =
InterpolatedNodalCurve.of(METADATA1, DoubleArray.of(0, 10), DoubleArray.of(0.01, 0.02), INTERPOLATOR);
private static final InterpolatedNodalCurve CURVE2 =
InterpolatedNodalCurve.of(METADATA2, DoubleArray.of(0, 10), DoubleArray.of(0.015, 0.025), INTERPOLATOR);
private static final ZeroRateDiscountFactors DFCURVE_GBP = ZeroRateDiscountFactors.of(GBP, DATE_VAL, CURVE1);
private static final ZeroRateDiscountFactors DFCURVE_GBP2 = ZeroRateDiscountFactors.of(GBP, DATE_VAL, CURVE2);
private static final ZeroRateDiscountFactors DFCURVE_USD = ZeroRateDiscountFactors.of(USD, DATE_VAL, CURVE2);
private static final ZeroRateDiscountFactors DFCURVE_USD2 = ZeroRateDiscountFactors.of(USD, DATE_VAL, CURVE1);
private static final RatesProvider PROVIDER = ImmutableRatesProvider.builder(DATE_VAL)
.discountCurve(GBP, CURVE1).discountCurve(USD, CURVE2)
.fxRateProvider(FX_RATE).build();
private static final DiscountingRatePaymentPeriodPricer PERIOD_PRICER = DiscountingRatePaymentPeriodPricer.DEFAULT;
private static final double TOLERANCE = 1.0E-6;
//-------------------------------------------------------------------------
public void test_of() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
assertEquals(test.getValuationDate(), DATE_VAL);
assertEquals(test.getBaseCurrencyDiscountFactors(), DFCURVE_GBP);
assertEquals(test.getCounterCurrencyDiscountFactors(), DFCURVE_USD);
assertEquals(test.getFxRateProvider(), FX_RATE);
assertEquals(test.findData(CURVE1.getName()), Optional.of(CURVE1));
assertEquals(test.findData(CURVE2.getName()), Optional.of(CURVE2));
assertEquals(test.findData(CurveName.of("Rubbish")), Optional.empty());
int baseSize = DFCURVE_USD.getParameterCount();
assertEquals(test.getParameterCount(), DFCURVE_GBP.getParameterCount() + baseSize);
assertEquals(test.getParameter(0), DFCURVE_GBP.getParameter(0));
assertEquals(test.getParameter(baseSize), DFCURVE_USD.getParameter(0));
assertEquals(test.getParameterMetadata(0), DFCURVE_GBP.getParameterMetadata(0));
assertEquals(test.getParameterMetadata(baseSize), DFCURVE_USD.getParameterMetadata(0));
assertEquals(test.withParameter(0, 1d).getBaseCurrencyDiscountFactors(), DFCURVE_GBP.withParameter(0, 1d));
assertEquals(test.withParameter(0, 1d).getCounterCurrencyDiscountFactors(), DFCURVE_USD);
assertEquals(test.withParameter(baseSize, 1d).getBaseCurrencyDiscountFactors(), DFCURVE_GBP);
assertEquals(test.withParameter(baseSize, 1d).getCounterCurrencyDiscountFactors(), DFCURVE_USD.withParameter(0, 1d));
assertEquals(
test.withPerturbation((i, v, m) -> v + 1d).getBaseCurrencyDiscountFactors(),
DFCURVE_GBP.withPerturbation((i, v, m) -> v + 1d));
assertEquals(
test.withPerturbation((i, v, m) -> v + 1d).getCounterCurrencyDiscountFactors(),
DFCURVE_USD.withPerturbation((i, v, m) -> v + 1d));
}
public void test_of_nonMatchingCurrency() {
assertThrowsIllegalArg(() -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_GBP));
assertThrowsIllegalArg(() -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_USD, DFCURVE_USD));
}
public void test_of_nonMatchingValuationDates() {
DiscountFactors curve2 = ZeroRateDiscountFactors.of(USD, DATE_REF, CURVE2);
assertThrowsIllegalArg(() -> DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, curve2));
}
public void test_builder() {
assertThrowsIllegalArg(() -> DiscountFxForwardRates.meta().builder()
.setString(DiscountFxForwardRates.meta().currencyPair(), "GBP/USD").build());
assertThrowsIllegalArg(() -> DiscountFxForwardRates.meta().builder()
.setString(DiscountFxForwardRates.meta().currencyPair().name(), "GBP/USD").build());
}
//-------------------------------------------------------------------------
public void test_withDiscountFactors() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
test = test.withDiscountFactors(DFCURVE_GBP2, DFCURVE_USD2);
assertEquals(test.getCurrencyPair(), CURRENCY_PAIR);
assertEquals(test.getValuationDate(), DATE_VAL);
assertEquals(test.getBaseCurrencyDiscountFactors(), DFCURVE_GBP2);
assertEquals(test.getCounterCurrencyDiscountFactors(), DFCURVE_USD2);
assertEquals(test.getFxRateProvider(), FX_RATE);
}
//-------------------------------------------------------------------------
public void test_rate() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
double dfCcyBaseAtMaturity = DFCURVE_GBP.discountFactor(DATE_REF);
double dfCcyCounterAtMaturity = DFCURVE_USD.discountFactor(DATE_REF);
double expected = FX_RATE.fxRate(GBP, USD) * (dfCcyBaseAtMaturity / dfCcyCounterAtMaturity);
assertEquals(test.rate(GBP, DATE_REF), expected, 1e-12);
assertEquals(test.rate(USD, DATE_REF), 1d / expected, 1e-12);
}
public void test_rate_nonMatchingCurrency() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
assertThrowsIllegalArg(() -> test.rate(EUR, DATE_VAL));
}
//-------------------------------------------------------------------------
public void test_ratePointSensitivity() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
assertEquals(test.ratePointSensitivity(GBP, DATE_REF),
FxForwardSensitivity.of(CURRENCY_PAIR, GBP, DATE_REF, 1d));
assertEquals(test.ratePointSensitivity(USD, DATE_REF),
FxForwardSensitivity.of(CURRENCY_PAIR, USD, DATE_REF, 1d));
}
public void test_ratePointSensitivity_nonMatchingCurrency() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
assertThrowsIllegalArg(() -> test.ratePointSensitivity(EUR, DATE_VAL));
}
//-------------------------------------------------------------------------
public void test_rateFxSpotSensitivity() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
double dfCcyBaseAtMaturity = DFCURVE_GBP.discountFactor(DATE_REF);
double dfCcyCounterAtMaturity = DFCURVE_USD.discountFactor(DATE_REF);
double expected = dfCcyBaseAtMaturity / dfCcyCounterAtMaturity;
assertEquals(test.rateFxSpotSensitivity(GBP, DATE_REF), expected, 1e-12);
assertEquals(test.rateFxSpotSensitivity(USD, DATE_REF), 1d / expected, 1e-12);
}
public void test_rateFxSpotSensitivity_nonMatchingCurrency() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
assertThrowsIllegalArg(() -> test.rateFxSpotSensitivity(EUR, DATE_VAL));
}
//-------------------------------------------------------------------------
//proper end-to-end tests are elsewhere
public void test_parameterSensitivity() {
DiscountFxForwardRates test = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
FxForwardSensitivity point = FxForwardSensitivity.of(CURRENCY_PAIR, GBP, DATE_VAL, 1d);
assertEquals(test.parameterSensitivity(point).size(), 2);
FxForwardSensitivity point2 = FxForwardSensitivity.of(CURRENCY_PAIR, USD, DATE_VAL, 1d);
assertEquals(test.parameterSensitivity(point2).size(), 2);
}
//-------------------------------------------------------------------------
public void currency_exposure_GBP() {
LocalDate startDate = LocalDate.of(2016, 8, 2);
LocalDate fixingDate = LocalDate.of(2016, 11, 2);
LocalDate endDate = LocalDate.of(2016, 11, 4);
double yearFraction = 0.25;
double rate = 0.10;
RateAccrualPeriod accrual = RateAccrualPeriod.builder().startDate(startDate)
.endDate(endDate).yearFraction(yearFraction).rateComputation(FixedRateComputation.of(rate)).build();
double notional = 1000000;
RatePaymentPeriod fixedFx = RatePaymentPeriod.builder()
.accrualPeriods(accrual)
.fxReset(FxReset.of(FxIndexObservation.of(FxIndices.GBP_USD_WM, fixingDate, REF_DATA), GBP))
.notional(notional)
.paymentDate(endDate)
.dayCount(DayCounts.ONE_ONE)
.currency(USD).build(); // 1_000_000 GBP paid in USD at maturity
PointSensitivityBuilder pts = PERIOD_PRICER.presentValueSensitivity(fixedFx, PROVIDER);
MultiCurrencyAmount ceComputed = PERIOD_PRICER.currencyExposure(fixedFx, PROVIDER);
double dfGbp = PROVIDER.discountFactor(GBP, endDate);
double ceGbpExpected = notional * yearFraction * rate * dfGbp;
assertEquals(ceComputed.getAmount(GBP).getAmount(), ceGbpExpected, 1.0E-6);
MultiCurrencyAmount ceWithoutPvComputed = PROVIDER.currencyExposure(pts.build().convertedTo(GBP, PROVIDER));
CurrencyAmount pvComputed = CurrencyAmount.of(USD, PERIOD_PRICER.presentValue(fixedFx, PROVIDER));
MultiCurrencyAmount ceComputed2 = ceWithoutPvComputed.plus(pvComputed);
assertEquals(ceComputed2.getAmount(GBP).getAmount(), ceGbpExpected, TOLERANCE);
assertEquals(ceComputed2.getAmount(USD).getAmount(), 0.0, TOLERANCE);
}
public void currency_exposure_USD() {
LocalDate startDate = LocalDate.of(2016, 8, 2);
LocalDate fixingDate = LocalDate.of(2016, 11, 2);
LocalDate endDate = LocalDate.of(2016, 11, 4);
double yearFraction = 0.25;
double rate = 0.10;
RateAccrualPeriod accrual = RateAccrualPeriod.builder().startDate(startDate)
.endDate(endDate).yearFraction(yearFraction).rateComputation(FixedRateComputation.of(rate)).build();
double notional = 1000000;
RatePaymentPeriod fixedFx = RatePaymentPeriod.builder()
.accrualPeriods(accrual)
.fxReset(FxReset.of(FxIndexObservation.of(FxIndices.GBP_USD_WM, fixingDate, REF_DATA), USD))
.notional(notional)
.paymentDate(endDate)
.dayCount(DayCounts.ONE_ONE)
.currency(GBP).build(); // 1_000_000 USD paid in GBP at maturity
PointSensitivityBuilder pts = PERIOD_PRICER.presentValueSensitivity(fixedFx, PROVIDER);
MultiCurrencyAmount ceComputed = PERIOD_PRICER.currencyExposure(fixedFx, PROVIDER);
double dfUsd = PROVIDER.discountFactor(USD, endDate);
double ceUsdExpected = notional * yearFraction * rate * dfUsd;
assertEquals(ceComputed.getAmount(USD).getAmount(), ceUsdExpected, 1.0E-6);
MultiCurrencyAmount ceWithoutPvComputed = PROVIDER.currencyExposure(pts.build().convertedTo(USD, PROVIDER));
CurrencyAmount pvComputed = CurrencyAmount.of(GBP, PERIOD_PRICER.presentValue(fixedFx, PROVIDER));
MultiCurrencyAmount ceComputed2 = ceWithoutPvComputed.plus(pvComputed);
assertEquals(ceComputed2.getAmount(USD).getAmount(), ceUsdExpected, TOLERANCE);
assertEquals(ceComputed2.getAmount(GBP).getAmount(), 0.0, TOLERANCE);
}
//-------------------------------------------------------------------------
public void coverage() {
DiscountFxForwardRates test1 = DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE, DFCURVE_GBP, DFCURVE_USD);
coverImmutableBean(test1);
DiscountFxForwardRates test2 =
DiscountFxForwardRates.of(CURRENCY_PAIR, FX_RATE.inverse(), DFCURVE_GBP2, DFCURVE_USD2);
coverBeanEquals(test1, test2);
DiscountFxForwardRates test3 = DiscountFxForwardRates.of(CurrencyPair.of(USD, EUR), FxRate.of(EUR, USD, 1.2d),
DFCURVE_USD, ZeroRateDiscountFactors.of(EUR, DATE_VAL, CURVE2));
coverBeanEquals(test1, test3);
}
}