/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.cms;
import static com.opengamma.strata.basics.currency.Currency.EUR;
import static com.opengamma.strata.basics.date.HolidayCalendarIds.EUTA;
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.assertTrue;
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.currency.CurrencyAmount;
import com.opengamma.strata.basics.date.BusinessDayAdjustment;
import com.opengamma.strata.basics.date.BusinessDayConventions;
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.collect.timeseries.LocalDateDoubleTimeSeries;
import com.opengamma.strata.market.explain.ExplainKey;
import com.opengamma.strata.market.explain.ExplainMap;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.sensitivity.PointSensitivities;
import com.opengamma.strata.market.sensitivity.PointSensitivityBuilder;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.pricer.sensitivity.RatesFiniteDifferenceSensitivityCalculator;
import com.opengamma.strata.pricer.swap.DiscountingSwapProductPricer;
import com.opengamma.strata.pricer.swaption.SabrParametersSwaptionVolatilities;
import com.opengamma.strata.pricer.swaption.SwaptionSabrRateVolatilityDataSet;
import com.opengamma.strata.product.cms.CmsLeg;
import com.opengamma.strata.product.cms.CmsPeriod;
import com.opengamma.strata.product.cms.ResolvedCmsLeg;
import com.opengamma.strata.product.swap.SwapIndex;
import com.opengamma.strata.product.swap.SwapIndices;
/**
* Test {@link SabrExtrapolationReplicationCmsLegPricer}.
*/
@Test
public class SabrExtrapolationReplicationCmsLegPricerTest {
private static final ReferenceData REF_DATA = ReferenceData.standard();
// CMS legs
private static final SwapIndex INDEX = SwapIndices.EUR_EURIBOR_1100_5Y;
private static final LocalDate START = LocalDate.of(2015, 10, 21);
private static final LocalDate END = LocalDate.of(2020, 10, 21);
private static final Frequency FREQUENCY = Frequency.P12M;
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 double CAP_VALUE = 0.0125;
private static final ValueSchedule CAP_STRIKE = ValueSchedule.of(CAP_VALUE);
private static final List<ValueStep> FLOOR_STEPS = new ArrayList<ValueStep>();
private static final List<ValueStep> NOTIONAL_STEPS = new ArrayList<ValueStep>();
private static final double FLOOR_VALUE_0 = 0.014;
private static final double FLOOR_VALUE_1 = 0.0135;
private static final double FLOOR_VALUE_2 = 0.012;
private static final double FLOOR_VALUE_3 = 0.013;
private static final double NOTIONAL_VALUE_0 = 1.0e6;
private static final double NOTIONAL_VALUE_1 = 1.1e6;
private static final double NOTIONAL_VALUE_2 = 0.9e6;
private static final double NOTIONAL_VALUE_3 = 1.2e6;
static {
FLOOR_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(FLOOR_VALUE_1)));
FLOOR_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(FLOOR_VALUE_2)));
FLOOR_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(FLOOR_VALUE_3)));
NOTIONAL_STEPS.add(ValueStep.of(1, ValueAdjustment.ofReplace(NOTIONAL_VALUE_1)));
NOTIONAL_STEPS.add(ValueStep.of(2, ValueAdjustment.ofReplace(NOTIONAL_VALUE_2)));
NOTIONAL_STEPS.add(ValueStep.of(3, ValueAdjustment.ofReplace(NOTIONAL_VALUE_3)));
}
private static final ValueSchedule FLOOR_STRIKE = ValueSchedule.of(FLOOR_VALUE_0, FLOOR_STEPS);
private static final ValueSchedule NOTIONAL = ValueSchedule.of(NOTIONAL_VALUE_0, NOTIONAL_STEPS);
private static final ResolvedCmsLeg CAP_LEG = CmsLeg.builder()
.capSchedule(CAP_STRIKE)
.index(INDEX)
.notional(NOTIONAL)
.payReceive(RECEIVE)
.paymentSchedule(SCHEDULE_EUR)
.build()
.resolve(REF_DATA);
private static final ResolvedCmsLeg FLOOR_LEG = CmsLeg.builder()
.floorSchedule(FLOOR_STRIKE)
.index(INDEX)
.notional(NOTIONAL)
.payReceive(RECEIVE)
.paymentSchedule(SCHEDULE_EUR)
.build()
.resolve(REF_DATA);
private static final ResolvedCmsLeg COUPON_LEG = CmsLeg.builder()
.index(INDEX)
.notional(NOTIONAL)
.payReceive(PAY)
.paymentSchedule(SCHEDULE_EUR)
.build()
.resolve(REF_DATA);
// providers
private static final LocalDate VALUATION = LocalDate.of(2015, 8, 18);
private static final ImmutableRatesProvider RATES_PROVIDER =
SwaptionSabrRateVolatilityDataSet.getRatesProviderEur(VALUATION);
private static final SabrParametersSwaptionVolatilities VOLATILITIES =
SwaptionSabrRateVolatilityDataSet.getVolatilitiesEur(VALUATION, true);
// providers - valuation after the first payment
private static final LocalDate AFTER_PAYMENT = LocalDate.of(2016, 11, 25); // the first cms payment is 2016-10-21.
private static final LocalDate FIXING = LocalDate.of(2016, 10, 19); // fixing for the second period.
private static final double OBS_INDEX = 0.013;
private static final LocalDateDoubleTimeSeries TIME_SERIES = LocalDateDoubleTimeSeries.of(FIXING, OBS_INDEX);
private static final ImmutableRatesProvider RATES_PROVIDER_AFTER_PERIOD =
SwaptionSabrRateVolatilityDataSet.getRatesProviderEur(AFTER_PAYMENT, TIME_SERIES);
private static final SabrParametersSwaptionVolatilities VOLATILITIES_AFTER_PERIOD =
SwaptionSabrRateVolatilityDataSet.getVolatilitiesEur(AFTER_PAYMENT, true);
// providers - valuation on the payment date
private static final LocalDate PAYMENT = LocalDate.of(2017, 10, 23); // payment date of the second payment
private static final ImmutableRatesProvider RATES_PROVIDER_ON_PAY =
SwaptionSabrRateVolatilityDataSet.getRatesProviderEur(PAYMENT, TIME_SERIES);
private static final SabrParametersSwaptionVolatilities VOLATILITIES_ON_PAY =
SwaptionSabrRateVolatilityDataSet.getVolatilitiesEur(PAYMENT, true);
// providers - valuation after maturity date
private static final LocalDate ENDED = END.plusDays(7);
private static final ImmutableRatesProvider RATES_PROVIDER_ENDED =
SwaptionSabrRateVolatilityDataSet.getRatesProviderEur(ENDED);
private static final SabrParametersSwaptionVolatilities VOLATILITIES_ENDED =
SwaptionSabrRateVolatilityDataSet.getVolatilitiesEur(ENDED, true);
// pricers
private static final double CUT_OFF_STRIKE = 0.10;
private static final double MU = 2.50;
private static final double EPS = 1.0e-5;
private static final double TOL = 1.0e-12;
private static final SabrExtrapolationReplicationCmsPeriodPricer PERIOD_PRICER =
SabrExtrapolationReplicationCmsPeriodPricer.of(CUT_OFF_STRIKE, MU);
private static final SabrExtrapolationReplicationCmsLegPricer LEG_PRICER =
new SabrExtrapolationReplicationCmsLegPricer(PERIOD_PRICER);
private static final RatesFiniteDifferenceSensitivityCalculator FD_CAL =
new RatesFiniteDifferenceSensitivityCalculator(EPS);
private static final DiscountingSwapProductPricer PRICER_SWAP =
DiscountingSwapProductPricer.DEFAULT;
//-------------------------------------------------------------------------
public void test_presentValue() {
CurrencyAmount computed = LEG_PRICER.presentValue(CAP_LEG, RATES_PROVIDER, VOLATILITIES);
double expected = 0d;
List<CmsPeriod> cms = CAP_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 0; i < size; ++i) {
expected += PERIOD_PRICER.presentValue(cms.get(i), RATES_PROVIDER, VOLATILITIES).getAmount();
}
assertEquals(computed.getCurrency(), EUR);
assertEquals(computed.getAmount(), expected, NOTIONAL_VALUE_0 * TOL);
}
public void test_presentValue_afterPay() {
CurrencyAmount computed = LEG_PRICER.presentValue(FLOOR_LEG, RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD);
double expected = 0d;
List<CmsPeriod> cms = FLOOR_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 1; i < size; ++i) {
expected += PERIOD_PRICER.presentValue(
cms.get(i), RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD).getAmount();
}
assertEquals(computed.getCurrency(), EUR);
assertEquals(computed.getAmount(), expected, NOTIONAL_VALUE_0 * TOL);
}
public void test_presentValue_ended() {
CurrencyAmount computed = LEG_PRICER.presentValue(COUPON_LEG, RATES_PROVIDER_ENDED, VOLATILITIES_ENDED);
assertEquals(computed, CurrencyAmount.zero(EUR));
}
//-------------------------------------------------------------------------
public void test_presentValueSensitivity() {
PointSensitivityBuilder point = LEG_PRICER.presentValueSensitivityRates(FLOOR_LEG, RATES_PROVIDER, VOLATILITIES);
CurrencyParameterSensitivities computed = RATES_PROVIDER.parameterSensitivity(point.build());
CurrencyParameterSensitivities expected =
FD_CAL.sensitivity(RATES_PROVIDER, p -> LEG_PRICER.presentValue(FLOOR_LEG, p, VOLATILITIES));
assertTrue(computed.equalWithTolerance(expected, EPS * NOTIONAL_VALUE_0 * 80d));
}
public void test_presentValueSensitivity_afterPay() {
PointSensitivityBuilder point =
LEG_PRICER.presentValueSensitivityRates(COUPON_LEG, RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD);
CurrencyParameterSensitivities computed = RATES_PROVIDER_AFTER_PERIOD.parameterSensitivity(point.build());
CurrencyParameterSensitivities expected = FD_CAL.sensitivity(
RATES_PROVIDER_AFTER_PERIOD, p -> LEG_PRICER.presentValue(COUPON_LEG, p, VOLATILITIES_AFTER_PERIOD));
assertTrue(computed.equalWithTolerance(expected, EPS * NOTIONAL_VALUE_0 * 10d));
}
public void test_presentValueSensitivity_ended() {
PointSensitivityBuilder computed = LEG_PRICER.presentValueSensitivityRates(CAP_LEG, RATES_PROVIDER_ENDED, VOLATILITIES_ENDED);
assertEquals(computed, PointSensitivityBuilder.none());
}
//-------------------------------------------------------------------------
public void test_presentValueSensitivitySabrParameter() {
PointSensitivityBuilder computed =
LEG_PRICER.presentValueSensitivityModelParamsSabr(FLOOR_LEG, RATES_PROVIDER, VOLATILITIES);
PointSensitivityBuilder expected = PointSensitivityBuilder.none();
List<CmsPeriod> cms = FLOOR_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 0; i < size; ++i) {
expected = expected.combinedWith(
PERIOD_PRICER.presentValueSensitivityModelParamsSabr(cms.get(i), RATES_PROVIDER, VOLATILITIES));
}
assertEquals(computed, expected);
}
public void test_presentValueSensitivitySabrParameter_afterPay() {
PointSensitivityBuilder computed =
LEG_PRICER.presentValueSensitivityModelParamsSabr(FLOOR_LEG, RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD);
PointSensitivityBuilder expected = PointSensitivityBuilder.none();
List<CmsPeriod> cms = FLOOR_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 0; i < size; ++i) {
expected = expected.combinedWith(PERIOD_PRICER.presentValueSensitivityModelParamsSabr(
cms.get(i), RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD));
}
assertEquals(computed, expected);
}
public void test_presentValueSensitivitySabrParameter_ended() {
PointSensitivities computed =
LEG_PRICER.presentValueSensitivityModelParamsSabr(CAP_LEG, RATES_PROVIDER_ENDED, VOLATILITIES_ENDED).build();
assertEquals(computed, PointSensitivities.empty());
}
//-------------------------------------------------------------------------
public void test_presentValueSensitivityStrike() {
double computed = LEG_PRICER.presentValueSensitivityStrike(CAP_LEG, RATES_PROVIDER, VOLATILITIES);
double expected = 0d;
List<CmsPeriod> cms = CAP_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 0; i < size; ++i) {
expected += PERIOD_PRICER.presentValueSensitivityStrike(cms.get(i), RATES_PROVIDER, VOLATILITIES);
}
assertEquals(computed, expected, NOTIONAL_VALUE_0 * TOL);
}
public void test_presentValueSensitivityStrike_afterPay() {
double computed = LEG_PRICER.presentValueSensitivityStrike(FLOOR_LEG, RATES_PROVIDER_AFTER_PERIOD,
VOLATILITIES_AFTER_PERIOD);
double expected = 0d;
List<CmsPeriod> cms = FLOOR_LEG.getCmsPeriods();
int size = cms.size();
for (int i = 1; i < size; ++i) {
expected += PERIOD_PRICER.presentValueSensitivityStrike(
cms.get(i), RATES_PROVIDER_AFTER_PERIOD, VOLATILITIES_AFTER_PERIOD);
}
assertEquals(computed, expected, NOTIONAL_VALUE_0 * TOL);
}
public void test_presentValueSensitivityStrike_ended() {
double computed = LEG_PRICER.presentValueSensitivityStrike(CAP_LEG, RATES_PROVIDER_ENDED, VOLATILITIES_ENDED);
assertEquals(computed, 0d);
}
//-------------------------------------------------------------------------
public void test_currentCash() {
CurrencyAmount computed = LEG_PRICER.currentCash(FLOOR_LEG, RATES_PROVIDER, VOLATILITIES);
assertEquals(computed, CurrencyAmount.zero(EUR));
}
public void test_currentCash_onPay() {
CurrencyAmount computed = LEG_PRICER.currentCash(CAP_LEG, RATES_PROVIDER_ON_PAY, VOLATILITIES_ON_PAY);
assertEquals(computed.getAmount(), NOTIONAL_VALUE_1 * (OBS_INDEX - CAP_VALUE) * 367d / 360d, NOTIONAL_VALUE_0 * TOL);
}
public void test_currentCash_twoPayments() {
ResolvedCmsLeg leg = ResolvedCmsLeg.builder()
.cmsPeriods(FLOOR_LEG.getCmsPeriods().get(1), CAP_LEG.getCmsPeriods().get(1))
.payReceive(RECEIVE)
.build();
CurrencyAmount computed = LEG_PRICER.currentCash(leg, RATES_PROVIDER_ON_PAY, VOLATILITIES_ON_PAY);
assertEquals(computed.getAmount(),
NOTIONAL_VALUE_1 * (OBS_INDEX - CAP_VALUE + FLOOR_VALUE_1 - OBS_INDEX) * 367d / 360d, NOTIONAL_VALUE_0 * TOL);
}
//-------------------------------------------------------------------------
public void test_explainPresentValue() {
ExplainMap explain = LEG_PRICER.explainPresentValue(CAP_LEG, RATES_PROVIDER, VOLATILITIES);
assertEquals(explain.get(ExplainKey.ENTRY_TYPE).get(), "CmsLeg");
assertEquals(explain.get(ExplainKey.PAY_RECEIVE).get().toString(), "Receive");
assertEquals(explain.get(ExplainKey.PAYMENT_CURRENCY).get().getCode(), "EUR");
assertEquals(explain.get(ExplainKey.START_DATE).get(), LocalDate.of(2015, 10, 21));
assertEquals(explain.get(ExplainKey.END_DATE).get(), LocalDate.of(2020, 10, 21));
assertEquals(explain.get(ExplainKey.INDEX).get().toString(), "EUR-EURIBOR-1100-5Y");
assertEquals(explain.get(ExplainKey.PRESENT_VALUE).get().getAmount(), 39728.51321029542);
List<ExplainMap> paymentPeriods = explain.get(ExplainKey.PAYMENT_PERIODS).get();
assertEquals(paymentPeriods.size(), 5);
//Test First Period
ExplainMap cmsPeriod0 = paymentPeriods.get(0);
assertEquals(cmsPeriod0.get(ExplainKey.ENTRY_TYPE).get(), "CmsCapletPeriod");
assertEquals(cmsPeriod0.get(ExplainKey.STRIKE_VALUE).get(), 0.0125d);
assertEquals(cmsPeriod0.get(ExplainKey.NOTIONAL).get().getAmount(), 1000000d);
assertEquals(cmsPeriod0.get(ExplainKey.PAYMENT_DATE).get(), LocalDate.of(2016, 10, 21));
assertEquals(cmsPeriod0.get(ExplainKey.DISCOUNT_FACTOR).get(), 0.9820085531995826d);
assertEquals(cmsPeriod0.get(ExplainKey.START_DATE).get(), LocalDate.of(2015, 10, 21));
assertEquals(cmsPeriod0.get(ExplainKey.END_DATE).get(), LocalDate.of(2016, 10, 21));
assertEquals(cmsPeriod0.get(ExplainKey.FIXING_DATE).get(), LocalDate.of(2015, 10, 19));
assertEquals(cmsPeriod0.get(ExplainKey.ACCRUAL_YEAR_FRACTION).get(), 1.0166666666666666d);
double forwardSwapRate = PRICER_SWAP.parRate(CAP_LEG.getCmsPeriods().get(0).getUnderlyingSwap(), RATES_PROVIDER);
assertEquals(cmsPeriod0.get(ExplainKey.FORWARD_RATE).get(), forwardSwapRate);
CurrencyAmount pv = PERIOD_PRICER.presentValue(CAP_LEG.getCmsPeriods().get(0), RATES_PROVIDER, VOLATILITIES);
assertEquals(cmsPeriod0.get(ExplainKey.PRESENT_VALUE).get(), pv);
}
}