/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.pricer.impl.rate.swap; import static com.opengamma.strata.basics.currency.Currency.GBP; import static com.opengamma.strata.basics.date.DayCounts.ACT_365F; import static com.opengamma.strata.basics.index.IborIndices.GBP_LIBOR_3M; import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg; import static com.opengamma.strata.collect.TestHelper.date; import static com.opengamma.strata.product.common.PayReceive.PAY; import static com.opengamma.strata.product.common.PayReceive.RECEIVE; import static com.opengamma.strata.product.swap.SwapLegType.FIXED; import static com.opengamma.strata.product.swap.SwapLegType.IBOR; import static com.opengamma.strata.product.swap.SwapLegType.OTHER; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import java.time.LocalDate; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.math.DoubleMath; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.CurrencyAmount; import com.opengamma.strata.basics.currency.MultiCurrencyAmount; import com.opengamma.strata.basics.currency.Payment; import com.opengamma.strata.market.param.CurrencyParameterSensitivities; import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder; import com.opengamma.strata.pricer.datasets.RatesProviderDataSets; import com.opengamma.strata.pricer.rate.ImmutableRatesProvider; import com.opengamma.strata.pricer.sensitivity.RatesFiniteDifferenceSensitivityCalculator; import com.opengamma.strata.pricer.swap.DiscountingSwapLegPricer; import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer; import com.opengamma.strata.product.rate.FixedRateComputation; import com.opengamma.strata.product.rate.IborRateComputation; import com.opengamma.strata.product.swap.NotionalExchange; import com.opengamma.strata.product.swap.SwapPaymentEvent; import com.opengamma.strata.product.swap.RateAccrualPeriod; import com.opengamma.strata.product.swap.RatePaymentPeriod; import com.opengamma.strata.product.swap.ResolvedSwap; import com.opengamma.strata.product.swap.ResolvedSwapLeg; /** * Test {@link CashFlowEquivalentCalculator}. */ @Test public class CashFlowEquivalentCalculatorTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); // setup private static final LocalDate PAYMENT1 = date(2014, 10, 6); private static final LocalDate START1 = date(2014, 7, 2); private static final LocalDate END1 = date(2014, 10, 2); private static final LocalDate FIXING1 = date(2014, 6, 30); private static final double PAY_YC1 = 0.251; private static final LocalDate PAYMENT2 = date(2015, 1, 4); private static final LocalDate START2 = date(2014, 10, 2); private static final LocalDate END2 = date(2015, 1, 2); private static final LocalDate FIXING2 = date(2014, 9, 30); private static final double PAY_YC2 = 0.249; private static final double RATE = 0.0123d; private static final double NOTIONAL = 100_000_000; private static final IborRateComputation GBP_LIBOR_3M_COMP1 = IborRateComputation.of(GBP_LIBOR_3M, FIXING1, REF_DATA); private static final IborRateComputation GBP_LIBOR_3M_COMP2 = IborRateComputation.of(GBP_LIBOR_3M, FIXING2, REF_DATA); // accrual periods private static final RateAccrualPeriod IBOR1 = RateAccrualPeriod.builder() .startDate(START1) .endDate(END1) .rateComputation(GBP_LIBOR_3M_COMP1) .yearFraction(PAY_YC1) .build(); private static final RateAccrualPeriod IBOR2 = RateAccrualPeriod.builder() .startDate(START2) .endDate(END2) .rateComputation(GBP_LIBOR_3M_COMP2) .yearFraction(PAY_YC2) .build(); private static final RateAccrualPeriod FIXED1 = RateAccrualPeriod.builder() .startDate(START1) .endDate(END1) .rateComputation(FixedRateComputation.of(RATE)) .yearFraction(PAY_YC1) .build(); private static final RateAccrualPeriod FIXED2 = RateAccrualPeriod.builder() .startDate(START2) .endDate(END2) .rateComputation(FixedRateComputation.of(RATE)) .yearFraction(PAY_YC2) .build(); //Ibor leg private static final RatePaymentPeriod IBOR_RATE_PAYMENT1 = RatePaymentPeriod.builder() .paymentDate(PAYMENT1) .accrualPeriods(IBOR1) .dayCount(ACT_365F) .currency(GBP) .notional(-NOTIONAL) .build(); private static final RatePaymentPeriod IBOR_RATE_PAYMENT2 = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(IBOR2) .dayCount(ACT_365F) .currency(GBP) .notional(-NOTIONAL) .build(); private static final ResolvedSwapLeg IBOR_LEG = ResolvedSwapLeg.builder() .type(IBOR) .payReceive(PAY) .paymentPeriods(IBOR_RATE_PAYMENT1, IBOR_RATE_PAYMENT2) .build(); // fixed leg private static final RatePaymentPeriod FIXED_RATE_PAYMENT1 = RatePaymentPeriod.builder() .paymentDate(PAYMENT1) .accrualPeriods(FIXED1) .dayCount(ACT_365F) .currency(GBP) .notional(NOTIONAL) .build(); private static final RatePaymentPeriod FIXED_RATE_PAYMENT2 = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(FIXED2) .dayCount(ACT_365F) .currency(GBP) .notional(NOTIONAL) .build(); private static final ResolvedSwapLeg FIXED_LEG = ResolvedSwapLeg.builder() .type(FIXED) .payReceive(RECEIVE) .paymentPeriods(FIXED_RATE_PAYMENT1, FIXED_RATE_PAYMENT2) .build(); private static final ImmutableRatesProvider PROVIDER = RatesProviderDataSets.MULTI_GBP; private static final double TOLERANCE_PV = 1.0E-2; public void test_cashFlowEquivalent() { ResolvedSwap swap = ResolvedSwap.of(IBOR_LEG, FIXED_LEG); ResolvedSwapLeg computed = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, PROVIDER); ResolvedSwapLeg computedIborLeg = CashFlowEquivalentCalculator.cashFlowEquivalentIborLeg(IBOR_LEG, PROVIDER); ResolvedSwapLeg computedFixedLeg = CashFlowEquivalentCalculator.cashFlowEquivalentFixedLeg(FIXED_LEG, PROVIDER); assertEquals(computedFixedLeg.getPaymentEvents(), computed.getPaymentEvents().subList(0, 2)); assertEquals(computedIborLeg.getPaymentEvents(), computed.getPaymentEvents().subList(2, 6)); // expected payments from fixed leg NotionalExchange fixedPayment1 = NotionalExchange.of(CurrencyAmount.of(GBP, NOTIONAL * RATE * PAY_YC1), PAYMENT1); NotionalExchange fixedPayment2 = NotionalExchange.of(CurrencyAmount.of(GBP, NOTIONAL * RATE * PAY_YC2), PAYMENT2); // expected payments from ibor leg LocalDate fixingSTART1 = GBP_LIBOR_3M.calculateEffectiveFromFixing(FIXING1, REF_DATA); double fixedYearFraction1 = GBP_LIBOR_3M.getDayCount().relativeYearFraction(fixingSTART1, GBP_LIBOR_3M.calculateMaturityFromEffective(fixingSTART1, REF_DATA)); double beta1 = (1d + fixedYearFraction1 * PROVIDER.iborIndexRates(GBP_LIBOR_3M).rate(GBP_LIBOR_3M_COMP1.getObservation())) * PROVIDER.discountFactor(GBP, PAYMENT1) / PROVIDER.discountFactor(GBP, fixingSTART1); NotionalExchange iborPayment11 = NotionalExchange.of(CurrencyAmount.of(GBP, -NOTIONAL * beta1 * PAY_YC1 / fixedYearFraction1), fixingSTART1); NotionalExchange iborPayment12 = NotionalExchange.of(CurrencyAmount.of(GBP, NOTIONAL * PAY_YC1 / fixedYearFraction1), PAYMENT1); LocalDate fixingSTART2 = GBP_LIBOR_3M.calculateEffectiveFromFixing(FIXING2, REF_DATA); double fixedYearFraction2 = GBP_LIBOR_3M.getDayCount().relativeYearFraction(fixingSTART2, GBP_LIBOR_3M.calculateMaturityFromEffective(fixingSTART2, REF_DATA)); double beta2 = (1d + fixedYearFraction2 * PROVIDER.iborIndexRates(GBP_LIBOR_3M).rate(GBP_LIBOR_3M_COMP2.getObservation())) * PROVIDER.discountFactor(GBP, PAYMENT2) / PROVIDER.discountFactor(GBP, fixingSTART2); NotionalExchange iborPayment21 = NotionalExchange.of(CurrencyAmount.of(GBP, -NOTIONAL * beta2 * PAY_YC2 / fixedYearFraction2), fixingSTART2); NotionalExchange iborPayment22 = NotionalExchange.of(CurrencyAmount.of(GBP, NOTIONAL * PAY_YC2 / fixedYearFraction2), PAYMENT2); ResolvedSwapLeg expected = ResolvedSwapLeg .builder() .type(OTHER) .payReceive(RECEIVE) .paymentEvents(fixedPayment1, fixedPayment2, iborPayment11, iborPayment12, iborPayment21, iborPayment22) .build(); double eps = 1.0e-12; assertEquals(computed.getPaymentEvents().size(), expected.getPaymentEvents().size()); for (int i = 0; i < 6; ++i) { NotionalExchange payCmp = (NotionalExchange) computed.getPaymentEvents().get(i); NotionalExchange payExp = (NotionalExchange) expected.getPaymentEvents().get(i); assertEquals(payCmp.getCurrency(), payExp.getCurrency()); assertEquals(payCmp.getPaymentDate(), payExp.getPaymentDate()); assertTrue(DoubleMath.fuzzyEquals(payCmp.getPaymentAmount().getAmount(), payExp.getPaymentAmount().getAmount(), NOTIONAL * eps)); } } public void test_cashFlowEquivalent_pv() { ResolvedSwap swap = ResolvedSwap.of(IBOR_LEG, FIXED_LEG); ResolvedSwapLeg cfe = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, PROVIDER); DiscountingSwapLegPricer pricerLeg = DiscountingSwapLegPricer.DEFAULT; DiscountingSwapProductPricer pricerSwap = DiscountingSwapProductPricer.DEFAULT; CurrencyAmount pvCfe = pricerLeg.presentValue(cfe, PROVIDER); MultiCurrencyAmount pvSwap = pricerSwap.presentValue(swap, PROVIDER); assertEquals(pvCfe.getAmount(), pvSwap.getAmount(GBP).getAmount(), TOLERANCE_PV); } public void test_cashFlowEquivalent_compounding() { RatePaymentPeriod iborCmp = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(IBOR1, IBOR2) .dayCount(ACT_365F) .currency(GBP) .notional(-NOTIONAL) .build(); ResolvedSwapLeg iborLegCmp = ResolvedSwapLeg.builder() .type(IBOR) .payReceive(PAY) .paymentPeriods(iborCmp) .build(); ResolvedSwap swap1 = ResolvedSwap.of(iborLegCmp, FIXED_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap1, PROVIDER)); RatePaymentPeriod fixedCmp = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(FIXED1, FIXED2) .dayCount(ACT_365F) .currency(GBP) .notional(NOTIONAL) .build(); ResolvedSwapLeg fixedLegCmp = ResolvedSwapLeg.builder() .type(FIXED) .payReceive(RECEIVE) .paymentPeriods(fixedCmp) .build(); ResolvedSwap swap2 = ResolvedSwap.of(IBOR_LEG, fixedLegCmp); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap2, PROVIDER)); } public void test_cashFlowEquivalent_wrongSwap() { ResolvedSwap swap1 = ResolvedSwap.of(IBOR_LEG, FIXED_LEG, IBOR_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap1, PROVIDER)); ResolvedSwap swap2 = ResolvedSwap.of(FIXED_LEG, FIXED_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap2, PROVIDER)); ResolvedSwap swap3 = ResolvedSwap.of( FIXED_LEG, CashFlowEquivalentCalculator.cashFlowEquivalentIborLeg(IBOR_LEG, PROVIDER)); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap3, PROVIDER)); } //------------------------------------------------------------------------- public void test_cashFlowEquivalentAndSensitivity() { ResolvedSwap swap = ResolvedSwap.of(IBOR_LEG, FIXED_LEG); ImmutableMap<Payment, PointSensitivityBuilder> computedFull = CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap, PROVIDER); ImmutableList<Payment> keyComputedFull = computedFull.keySet().asList(); ImmutableList<PointSensitivityBuilder> valueComputedFull = computedFull.values().asList(); ImmutableMap<Payment, PointSensitivityBuilder> computedIborLeg = CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivityIborLeg(IBOR_LEG, PROVIDER); ImmutableMap<Payment, PointSensitivityBuilder> computedFixedLeg = CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivityFixedLeg(FIXED_LEG, PROVIDER); assertEquals(computedFixedLeg.keySet().asList(), keyComputedFull.subList(0, 2)); assertEquals(computedIborLeg.keySet().asList(), keyComputedFull.subList(2, 6)); assertEquals(computedFixedLeg.values().asList(), valueComputedFull.subList(0, 2)); assertEquals(computedIborLeg.values().asList(), valueComputedFull.subList(2, 6)); double eps = 1.0e-7; RatesFiniteDifferenceSensitivityCalculator calc = new RatesFiniteDifferenceSensitivityCalculator(eps); int size = keyComputedFull.size(); for (int i = 0; i < size; ++i) { final int index = i; CurrencyParameterSensitivities expected = calc.sensitivity(PROVIDER, p -> ((NotionalExchange) CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, p) .getPaymentEvents().get(index)).getPaymentAmount()); SwapPaymentEvent event = CashFlowEquivalentCalculator.cashFlowEquivalentSwap(swap, PROVIDER).getPaymentEvents().get(index); PointSensitivityBuilder point = computedFull.get(((NotionalExchange) event).getPayment()); CurrencyParameterSensitivities computed = PROVIDER.parameterSensitivity(point.build()); assertTrue(computed.equalWithTolerance(expected, eps * NOTIONAL)); } } public void test_cashFlowEquivalentAndSensitivity_compounding() { RatePaymentPeriod iborCmp = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(IBOR1, IBOR2) .dayCount(ACT_365F) .currency(GBP) .notional(-NOTIONAL) .build(); ResolvedSwapLeg iborLegCmp = ResolvedSwapLeg.builder() .type(IBOR) .payReceive(PAY) .paymentPeriods(iborCmp) .build(); ResolvedSwap swap1 = ResolvedSwap.of(iborLegCmp, FIXED_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap1, PROVIDER)); RatePaymentPeriod fixedCmp = RatePaymentPeriod.builder() .paymentDate(PAYMENT2) .accrualPeriods(FIXED1, FIXED2) .dayCount(ACT_365F) .currency(GBP) .notional(NOTIONAL) .build(); ResolvedSwapLeg fixedLegCmp = ResolvedSwapLeg.builder() .type(FIXED) .payReceive(RECEIVE) .paymentPeriods(fixedCmp) .build(); ResolvedSwap swap2 = ResolvedSwap.of(IBOR_LEG, fixedLegCmp); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap2, PROVIDER)); } public void test_cashFlowEquivalentAndSensitivity_wrongSwap() { ResolvedSwap swap1 = ResolvedSwap.of(IBOR_LEG, FIXED_LEG, IBOR_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap1, PROVIDER)); ResolvedSwap swap2 = ResolvedSwap.of(FIXED_LEG, FIXED_LEG); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap2, PROVIDER)); ResolvedSwap swap3 = ResolvedSwap.of(FIXED_LEG, CashFlowEquivalentCalculator.cashFlowEquivalentIborLeg(IBOR_LEG, PROVIDER)); assertThrowsIllegalArg(() -> CashFlowEquivalentCalculator.cashFlowEquivalentAndSensitivitySwap(swap3, PROVIDER)); } }