/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.product.cms; import static com.opengamma.strata.basics.currency.Currency.EUR; import static com.opengamma.strata.basics.currency.Currency.GBP; import static com.opengamma.strata.basics.date.DayCounts.ACT_365_ACTUAL; import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA; import static com.opengamma.strata.basics.date.HolidayCalendarIds.SAT_SUN; import static com.opengamma.strata.basics.index.IborIndices.EUR_EURIBOR_6M; import static com.opengamma.strata.collect.TestHelper.assertSerialization; 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.product.common.PayReceive.PAY; import static com.opengamma.strata.product.common.PayReceive.RECEIVE; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import org.testng.annotations.Test; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.date.AdjustableDate; import com.opengamma.strata.basics.date.BusinessDayAdjustment; import com.opengamma.strata.basics.date.BusinessDayConventions; import com.opengamma.strata.basics.date.DaysAdjustment; import com.opengamma.strata.basics.schedule.Frequency; import com.opengamma.strata.basics.schedule.PeriodicSchedule; import com.opengamma.strata.basics.schedule.RollConventions; import com.opengamma.strata.basics.schedule.StubConvention; import com.opengamma.strata.basics.value.ValueAdjustment; import com.opengamma.strata.basics.value.ValueSchedule; import com.opengamma.strata.basics.value.ValueStep; import com.opengamma.strata.product.common.BuySell; import com.opengamma.strata.product.swap.FixingRelativeTo; import com.opengamma.strata.product.swap.ResolvedSwap; import com.opengamma.strata.product.swap.Swap; import com.opengamma.strata.product.swap.SwapIndex; import com.opengamma.strata.product.swap.SwapIndices; import com.opengamma.strata.product.swap.type.FixedIborSwapConvention; /** * Test {@link CmsLeg}. */ @Test public class CmsLegTest { private static final ReferenceData REF_DATA = ReferenceData.standard(); private static final SwapIndex INDEX = SwapIndices.EUR_EURIBOR_1100_10Y; private static final LocalDate START = LocalDate.of(2015, 10, 21); private static final LocalDate END = LocalDate.of(2017, 10, 21); private static final Frequency FREQUENCY = Frequency.P12M; private static final BusinessDayAdjustment BUSS_ADJ = BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, SAT_SUN); private static final PeriodicSchedule SCHEDULE = PeriodicSchedule.builder() .startDate(START) .endDate(END) .frequency(FREQUENCY) .businessDayAdjustment(BUSS_ADJ) .build(); private static final BusinessDayAdjustment BUSS_ADJ_EUR = BusinessDayAdjustment.of(BusinessDayConventions.FOLLOWING, EUTA); private static final PeriodicSchedule SCHEDULE_EUR = PeriodicSchedule.of(START, END, FREQUENCY, BUSS_ADJ_EUR, StubConvention.NONE, RollConventions.NONE); private static final DaysAdjustment FIXING_OFFSET = DaysAdjustment.ofBusinessDays(-3, SAT_SUN); private static final DaysAdjustment PAYMENT_OFFSET = DaysAdjustment.ofBusinessDays(2, SAT_SUN); private static final ValueSchedule CAP = ValueSchedule.of(0.0125); private static final List<ValueStep> FLOOR_STEPS = new ArrayList<ValueStep>(); private static final List<ValueStep> NOTIONAL_STEPS = new ArrayList<ValueStep>(); static { FLOOR_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(0.02))); NOTIONAL_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(1.2e6))); } private static final ValueSchedule FLOOR = ValueSchedule.of(0.011, FLOOR_STEPS); private static final ValueSchedule NOTIONAL = ValueSchedule.of(1.0e6, NOTIONAL_STEPS); public void test_builder_full() { CmsLeg test = CmsLeg.builder() .currency(GBP) .dayCount(ACT_365_ACTUAL) .fixingRelativeTo(FixingRelativeTo.PERIOD_END) .fixingDateOffset(FIXING_OFFSET) .paymentDateOffset(PAYMENT_OFFSET) .floorSchedule(FLOOR) .index(INDEX) .notional(NOTIONAL) .payReceive(PAY) .paymentSchedule(SCHEDULE) .build(); assertEquals(test.getPayReceive(), PAY); assertFalse(test.getCapSchedule().isPresent()); assertEquals(test.getFloorSchedule().get(), FLOOR); assertEquals(test.getCurrency(), GBP); assertEquals(test.getNotional(), NOTIONAL); assertEquals(test.getDayCount(), ACT_365_ACTUAL); assertEquals(test.getStartDate(), AdjustableDate.of(START, SCHEDULE.getBusinessDayAdjustment())); assertEquals(test.getEndDate(), SCHEDULE.calculatedEndDate()); assertEquals(test.getIndex(), INDEX); assertEquals(test.getPaymentSchedule(), SCHEDULE); assertEquals(test.getFixingRelativeTo(), FixingRelativeTo.PERIOD_END); assertEquals(test.getFixingDateOffset(), FIXING_OFFSET); assertEquals(test.getPaymentDateOffset(), PAYMENT_OFFSET); } public void test_builder_full_coupon() { CmsLeg test = CmsLeg.builder() .currency(GBP) .dayCount(ACT_365_ACTUAL) .fixingRelativeTo(FixingRelativeTo.PERIOD_END) .fixingDateOffset(FIXING_OFFSET) .paymentDateOffset(PAYMENT_OFFSET) .index(INDEX) .notional(NOTIONAL) .payReceive(PAY) .paymentSchedule(SCHEDULE) .build(); assertEquals(test.getPayReceive(), PAY); assertFalse(test.getCapSchedule().isPresent()); assertFalse(test.getFloorSchedule().isPresent()); assertEquals(test.getCurrency(), GBP); assertEquals(test.getNotional(), NOTIONAL); assertEquals(test.getDayCount(), ACT_365_ACTUAL); assertEquals(test.getStartDate(), AdjustableDate.of(START, SCHEDULE.getBusinessDayAdjustment())); assertEquals(test.getEndDate(), SCHEDULE.calculatedEndDate()); assertEquals(test.getIndex(), INDEX); assertEquals(test.getPaymentSchedule(), SCHEDULE); assertEquals(test.getFixingRelativeTo(), FixingRelativeTo.PERIOD_END); assertEquals(test.getFixingDateOffset(), FIXING_OFFSET); assertEquals(test.getPaymentDateOffset(), PAYMENT_OFFSET); } public void test_builder_min() { CmsLeg test = sutCap(); assertEquals(test.getPayReceive(), RECEIVE); assertEquals(test.getCapSchedule().get(), CAP); assertFalse(test.getFloorSchedule().isPresent()); assertEquals(test.getCurrency(), EUR_EURIBOR_6M.getCurrency()); assertEquals(test.getNotional(), NOTIONAL); assertEquals(test.getDayCount(), EUR_EURIBOR_6M.getDayCount()); assertEquals(test.getStartDate(), AdjustableDate.of(START, SCHEDULE_EUR.getBusinessDayAdjustment())); assertEquals(test.getEndDate(), SCHEDULE_EUR.calculatedEndDate()); assertEquals(test.getIndex(), INDEX); assertEquals(test.getPaymentSchedule(), SCHEDULE_EUR); assertEquals(test.getFixingRelativeTo(), FixingRelativeTo.PERIOD_START); assertEquals(test.getFixingDateOffset(), EUR_EURIBOR_6M.getFixingDateOffset()); assertEquals(test.getPaymentDateOffset(), DaysAdjustment.NONE); } public void test_builder_min_coupon() { CmsLeg test = CmsLeg.builder() .index(INDEX) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule(SCHEDULE_EUR) .build(); assertEquals(test.getPayReceive(), RECEIVE); assertFalse(test.getCapSchedule().isPresent()); assertFalse(test.getFloorSchedule().isPresent()); assertEquals(test.getCurrency(), EUR_EURIBOR_6M.getCurrency()); assertEquals(test.getNotional(), NOTIONAL); assertEquals(test.getDayCount(), EUR_EURIBOR_6M.getDayCount()); assertEquals(test.getStartDate(), AdjustableDate.of(START, SCHEDULE_EUR.getBusinessDayAdjustment())); assertEquals(test.getEndDate(), SCHEDULE_EUR.calculatedEndDate()); assertEquals(test.getIndex(), INDEX); assertEquals(test.getPaymentSchedule(), SCHEDULE_EUR); assertEquals(test.getFixingRelativeTo(), FixingRelativeTo.PERIOD_START); assertEquals(test.getFixingDateOffset(), EUR_EURIBOR_6M.getFixingDateOffset()); assertEquals(test.getPaymentDateOffset(), DaysAdjustment.NONE); } public void test_builder_fail() { // index is null assertThrowsIllegalArg(() -> CmsLeg.builder() .capSchedule(CAP) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule(SCHEDULE_EUR) .build()); // floorSchedule and capSchedule are present assertThrowsIllegalArg(() -> CmsLeg.builder() .capSchedule(CAP) .floorSchedule(FLOOR) .index(INDEX) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule(SCHEDULE_EUR) .build()); // stub is on assertThrowsIllegalArg(() -> CmsLeg .builder() .index(INDEX) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule( PeriodicSchedule.of(START, END, FREQUENCY, BUSS_ADJ_EUR, StubConvention.SHORT_INITIAL, RollConventions.NONE)) .build()); } public void test_resolve() { CmsLeg baseFloor = CmsLeg.builder() .floorSchedule(FLOOR) .index(INDEX) .notional(NOTIONAL) .payReceive(PAY) .paymentSchedule(SCHEDULE_EUR) .build(); ResolvedCmsLeg resolvedFloor = baseFloor.resolve(REF_DATA); LocalDate end1 = LocalDate.of(2016, 10, 21); LocalDate fixing1 = EUR_EURIBOR_6M.calculateFixingFromEffective(START, REF_DATA); LocalDate fixing2 = EUR_EURIBOR_6M.calculateFixingFromEffective(end1, REF_DATA); LocalDate fixing3 = EUR_EURIBOR_6M.calculateFixingFromEffective(END, REF_DATA); LocalDate endDate = SCHEDULE_EUR.calculatedEndDate().adjusted(REF_DATA); CmsPeriod period1 = CmsPeriod.builder() .currency(EUR) .floorlet(FLOOR.getInitialValue()) .notional(-NOTIONAL.getInitialValue()) .index(INDEX) .startDate(START) .endDate(end1) .unadjustedStartDate(START) .unadjustedEndDate(end1) .fixingDate(fixing1) .paymentDate(end1) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(START, end1)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing1)) .build(); CmsPeriod period2 = CmsPeriod.builder() .currency(EUR) .floorlet(FLOOR.getSteps().get(0).getValue().getModifyingValue()) .notional(-NOTIONAL.getSteps().get(0).getValue().getModifyingValue()) .index(INDEX) .startDate(end1) .endDate(endDate) .unadjustedStartDate(end1) .unadjustedEndDate(END) .fixingDate(fixing2) .paymentDate(endDate) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(end1, endDate)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing2)) .build(); assertEquals(resolvedFloor.getCurrency(), EUR); assertEquals(resolvedFloor.getStartDate(), baseFloor.getStartDate().adjusted(REF_DATA)); assertEquals(resolvedFloor.getEndDate(), baseFloor.getEndDate().adjusted(REF_DATA)); assertEquals(resolvedFloor.getIndex(), INDEX); assertEquals(resolvedFloor.getPayReceive(), PAY); assertEquals(resolvedFloor.getCmsPeriods().size(), 2); assertEquals(resolvedFloor.getCmsPeriods().get(0), period1); assertEquals(resolvedFloor.getCmsPeriods().get(1), period2); CmsLeg baseFloorEnd = CmsLeg.builder() .floorSchedule(FLOOR) .fixingRelativeTo(FixingRelativeTo.PERIOD_END) .index(INDEX) .notional(NOTIONAL) .payReceive(PAY) .paymentSchedule(SCHEDULE_EUR) .build(); ResolvedCmsLeg resolvedFloorEnd = baseFloorEnd.resolve(REF_DATA); CmsPeriod period1End = CmsPeriod.builder() .currency(EUR) .floorlet(FLOOR.getInitialValue()) .notional(-NOTIONAL.getInitialValue()) .index(INDEX) .startDate(START) .endDate(end1) .unadjustedStartDate(START) .unadjustedEndDate(end1) .fixingDate(fixing2) .paymentDate(end1) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(START, end1)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing2)) .build(); CmsPeriod period2End = CmsPeriod.builder() .currency(EUR) .floorlet(FLOOR.getSteps().get(0).getValue().getModifyingValue()) .notional(-NOTIONAL.getSteps().get(0).getValue().getModifyingValue()) .index(INDEX) .startDate(end1) .endDate(endDate) .unadjustedStartDate(end1) .unadjustedEndDate(END) .fixingDate(fixing3) .paymentDate(endDate) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(end1, endDate)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing3)) .build(); assertEquals(resolvedFloorEnd.getCurrency(), EUR); assertEquals(resolvedFloorEnd.getStartDate(), baseFloor.getStartDate().adjusted(REF_DATA)); assertEquals(resolvedFloorEnd.getEndDate(), baseFloor.getEndDate().adjusted(REF_DATA)); assertEquals(resolvedFloorEnd.getIndex(), INDEX); assertEquals(resolvedFloorEnd.getPayReceive(), PAY); assertEquals(resolvedFloorEnd.getCmsPeriods().size(), 2); assertEquals(resolvedFloorEnd.getCmsPeriods().get(0), period1End); assertEquals(resolvedFloorEnd.getCmsPeriods().get(1), period2End); CmsLeg baseCap = CmsLeg.builder() .index(INDEX) .capSchedule(CAP) .notional(NOTIONAL) .payReceive(PAY) .paymentSchedule(SCHEDULE_EUR) .paymentDateOffset(PAYMENT_OFFSET) .build(); ResolvedCmsLeg resolvedCap = baseCap.resolve(REF_DATA); CmsPeriod periodCap1 = CmsPeriod.builder() .currency(EUR) .notional(-NOTIONAL.getInitialValue()) .index(INDEX) .caplet(CAP.getInitialValue()) .startDate(START) .endDate(end1) .unadjustedStartDate(START) .unadjustedEndDate(end1) .fixingDate(fixing1) .paymentDate(PAYMENT_OFFSET.adjust(end1, REF_DATA)) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(START, end1)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing1)) .build(); CmsPeriod periodCap2 = CmsPeriod.builder() .currency(EUR) .notional(-NOTIONAL.getSteps().get(0).getValue().getModifyingValue()) .index(INDEX) .caplet(CAP.getInitialValue()) .startDate(end1) .endDate(endDate) .unadjustedStartDate(end1) .unadjustedEndDate(END) .fixingDate(fixing2) .paymentDate(PAYMENT_OFFSET.adjust(endDate, REF_DATA)) .yearFraction(EUR_EURIBOR_6M.getDayCount().yearFraction(end1, endDate)) .dayCount(EUR_EURIBOR_6M.getDayCount()) .underlyingSwap(createUnderlyingSwap(fixing2)) .build(); assertEquals(resolvedCap.getCurrency(), EUR); assertEquals(resolvedCap.getStartDate(), baseCap.getStartDate().adjusted(REF_DATA)); assertEquals(resolvedCap.getEndDate(), baseCap.getEndDate().adjusted(REF_DATA)); assertEquals(resolvedCap.getIndex(), INDEX); assertEquals(resolvedCap.getPayReceive(), PAY); assertEquals(resolvedCap.getCmsPeriods().size(), 2); assertEquals(resolvedCap.getCmsPeriods().get(0), periodCap1); assertEquals(resolvedCap.getCmsPeriods().get(1), periodCap2); } private ResolvedSwap createUnderlyingSwap(LocalDate fixingDate) { FixedIborSwapConvention conv = INDEX.getTemplate().getConvention(); LocalDate effectiveDate = conv.calculateSpotDateFromTradeDate(fixingDate, REF_DATA); LocalDate maturityDate = effectiveDate.plus(INDEX.getTemplate().getTenor()); Swap swap = conv.toTrade(fixingDate, effectiveDate, maturityDate, BuySell.BUY, 1d, 1d).getProduct(); return swap.resolve(REF_DATA); } //------------------------------------------------------------------------- public void coverage() { coverImmutableBean(sutCap()); coverBeanEquals(sutCap(), sutFloor()); } public void test_serialization() { assertSerialization(sutCap()); } //------------------------------------------------------------------------- static CmsLeg sutCap() { return CmsLeg.builder() .capSchedule(CAP) .index(INDEX) .notional(NOTIONAL) .payReceive(RECEIVE) .paymentSchedule(SCHEDULE_EUR) .build(); } static CmsLeg sutFloor() { return CmsLeg.builder() .floorSchedule(FLOOR) .index(SwapIndices.USD_LIBOR_1100_10Y) .notional(ValueSchedule.of(1.e6)) .payReceive(PAY) .paymentSchedule(SCHEDULE) .fixingRelativeTo(FixingRelativeTo.PERIOD_END) .fixingDateOffset(FIXING_OFFSET) .paymentDateOffset(FIXING_OFFSET) .dayCount(ACT_365_ACTUAL) .build(); } }