/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.interestrate.swaption.provider;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.datasets.CalendarUSD;
import com.opengamma.analytics.financial.instrument.NotionalProvider;
import com.opengamma.analytics.financial.instrument.annuity.AdjustedDateParameters;
import com.opengamma.analytics.financial.instrument.annuity.AnnuityCouponFixedDefinition;
import com.opengamma.analytics.financial.instrument.annuity.AnnuityCouponIborDefinition;
import com.opengamma.analytics.financial.instrument.annuity.AnnuityDefinition;
import com.opengamma.analytics.financial.instrument.annuity.FixedAnnuityDefinitionBuilder;
import com.opengamma.analytics.financial.instrument.annuity.FloatingAnnuityDefinitionBuilder;
import com.opengamma.analytics.financial.instrument.annuity.OffsetAdjustedDateParameters;
import com.opengamma.analytics.financial.instrument.annuity.OffsetType;
import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIbor;
import com.opengamma.analytics.financial.instrument.index.GeneratorSwapFixedIborMaster;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.analytics.financial.instrument.payment.CouponDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition;
import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborDefinition;
import com.opengamma.analytics.financial.instrument.swaption.SwaptionPhysicalFixedIborDefinition;
import com.opengamma.analytics.financial.interestrate.sensitivity.PresentValueSwaptionSurfaceSensitivity;
import com.opengamma.analytics.financial.interestrate.swap.provider.SwapFixedCouponDiscountingMethod;
import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionPhysicalFixedIbor;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalPriceFunction;
import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.normalswaption.PresentValueCurveSensitivityNormalSwaptionCalculator;
import com.opengamma.analytics.financial.provider.calculator.normalswaption.PresentValueNormalSwaptionCalculator;
import com.opengamma.analytics.financial.provider.description.MulticurveProviderDiscountDataSets;
import com.opengamma.analytics.financial.provider.description.NormalDataSets;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.NormalSwaptionExpiryTenorProvider;
import com.opengamma.analytics.financial.provider.description.interestrate.NormalSwaptionProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.normalswaption.ParameterSensitivityNormalSwaptionExpiryTenorDiscountInterpolatedFDCalculator;
import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator;
import com.opengamma.analytics.financial.util.AssertSensitivityObjects;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.rolldate.RollConvention;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.time.DateUtils;
/**
* Tests related to the pricing of swaptions with a normal (Bachelier) model.
*/
public class SwaptionPhysicalFixedIborNormalExpiryTenorMethodTest {
private static final ZonedDateTime VALUATION_DATE = DateUtils.getUTCDate(2014, 7, 16);
/** Conventions */
private static final Calendar NYC = new CalendarUSD("NYC");
private static final GeneratorSwapFixedIborMaster GENERATOR_IRS_MASTER = GeneratorSwapFixedIborMaster.getInstance();
private static final GeneratorSwapFixedIbor USD6MLIBOR3M = GENERATOR_IRS_MASTER.getGenerator("USD6MLIBOR3M", NYC);
private static final IborIndex USDLIBOR3M = USD6MLIBOR3M.getIborIndex();
private static final Currency USD = USDLIBOR3M.getCurrency();
private static final AdjustedDateParameters ADJUSTED_DATE_LIBOR =
new AdjustedDateParameters(NYC, USD6MLIBOR3M.getBusinessDayConvention());
private static final OffsetAdjustedDateParameters OFFSET_ADJ_LIBOR =
new OffsetAdjustedDateParameters(-2, OffsetType.BUSINESS, NYC, USD6MLIBOR3M.getBusinessDayConvention());
/** Data */
private static final MulticurveProviderDiscount MULTICURVE = MulticurveProviderDiscountDataSets.createMulticurveEurUsd();
private static final InterpolatedDoublesSurface NORMAL_SURFACE_SWAPTION_EXP_TENOR =
NormalDataSets.normalSurfaceSwaptionExpiryTenor();
private static final NormalSwaptionExpiryTenorProvider MULTICURVE_NEG_NORMAL =
new NormalSwaptionExpiryTenorProvider(MULTICURVE, NORMAL_SURFACE_SWAPTION_EXP_TENOR, USD6MLIBOR3M);
/** Swaption */
private static final ZonedDateTime EXPIRY_1_DATE = DateUtils.getUTCDate(2016, 7, 14);
private static final LocalDate SWAP_1_EFFECTIVE_DATE = LocalDate.of(2016, 7, 18);;
private static final Period SWAP_1_TENOR_1 = Period.ofYears(5);
private static final LocalDate SWAP_1_MATURITY_DATE = SWAP_1_EFFECTIVE_DATE.plus(SWAP_1_TENOR_1);
private static final double RATE_1 = 0.0150;
private static final double NOTIONAL_1 = 10_000_000;
private static final boolean PAYER_1 = true;
private static final boolean LONG_1 = true;
private static final SwapFixedIborDefinition SWAP_1_P_DEFINITION =
swap(SWAP_1_EFFECTIVE_DATE, SWAP_1_MATURITY_DATE, RATE_1, NOTIONAL_1, PAYER_1);
private static final SwapFixedIborDefinition SWAP_1_R_DEFINITION =
swap(SWAP_1_EFFECTIVE_DATE, SWAP_1_MATURITY_DATE, RATE_1, NOTIONAL_1, !PAYER_1);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_1_P_L_DEFINITION =
SwaptionPhysicalFixedIborDefinition.from(EXPIRY_1_DATE, SWAP_1_P_DEFINITION, PAYER_1, LONG_1);
private static final SwaptionPhysicalFixedIbor SWAPTION_1_P_L = SWAPTION_1_P_L_DEFINITION.toDerivative(VALUATION_DATE);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_1_P_S_DEFINITION =
SwaptionPhysicalFixedIborDefinition.from(EXPIRY_1_DATE, SWAP_1_P_DEFINITION, PAYER_1, !LONG_1);
private static final SwaptionPhysicalFixedIbor SWAPTION_1_P_S = SWAPTION_1_P_S_DEFINITION.toDerivative(VALUATION_DATE);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_1_R_S_DEFINITION =
SwaptionPhysicalFixedIborDefinition.from(EXPIRY_1_DATE, SWAP_1_R_DEFINITION, !PAYER_1, !LONG_1);
private static final SwaptionPhysicalFixedIbor SWAPTION_1_R_S = SWAPTION_1_R_S_DEFINITION.toDerivative(VALUATION_DATE);
/** Swaption with negative strike */
private static final ZonedDateTime EXPIRY_2_DATE = DateUtils.getUTCDate(2016, 7, 14);
private static final LocalDate SWAP_2_EFFECTIVE_DATE = LocalDate.of(2016, 7, 18);;
private static final Period SWAP_2_TENOR_1 = Period.ofYears(5);
private static final LocalDate SWAP_2_MATURITY_DATE = SWAP_2_EFFECTIVE_DATE.plus(SWAP_2_TENOR_1);
private static final double RATE_2 = -0.025;
private static final double NOTIONAL_2 = 10_000_000;
private static final boolean PAYER_2 = false;
private static final boolean LONG_2 = true;
private static final SwapFixedIborDefinition SWAP_2_DEFINITION =
swap(SWAP_2_EFFECTIVE_DATE, SWAP_2_MATURITY_DATE, RATE_2, NOTIONAL_2, PAYER_2);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_2_DEFINITION =
SwaptionPhysicalFixedIborDefinition.from(EXPIRY_2_DATE, SWAP_2_DEFINITION, PAYER_2, LONG_2);
private static final SwaptionPhysicalFixedIbor SWAPTION_2 = SWAPTION_2_DEFINITION.toDerivative(VALUATION_DATE);
/** Calculators and methods */
private static final SwapFixedCouponDiscountingMethod METHOD_SWAP = SwapFixedCouponDiscountingMethod.getInstance();
private static final ParRateDiscountingCalculator PRDC = ParRateDiscountingCalculator.getInstance();
private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance();
private static final PresentValueNormalSwaptionCalculator PVNSC = PresentValueNormalSwaptionCalculator.getInstance();
private static final PresentValueCurveSensitivityNormalSwaptionCalculator PVCSNSC =
PresentValueCurveSensitivityNormalSwaptionCalculator.getInstance();
private static final SwaptionPhysicalFixedIborNormalMethod METHOD_SWPT_NORMAL =
SwaptionPhysicalFixedIborNormalMethod.getInstance();
private static final ParameterSensitivityParameterCalculator<NormalSwaptionProviderInterface> PS =
new ParameterSensitivityParameterCalculator<>(PVCSNSC);
private static final double SHIFT_FD = 1.0E-6;
private static final ParameterSensitivityNormalSwaptionExpiryTenorDiscountInterpolatedFDCalculator PS_FD =
new ParameterSensitivityNormalSwaptionExpiryTenorDiscountInterpolatedFDCalculator(PVNSC, SHIFT_FD);
private static final double TOLERANCE_PV = 1.0E-2;
private static final double TOLERANCE_PV_DELTA = 1.0E+2;
private static final double TOLERANCE_VOL = 1.0E-8;
@Test
public void impliedVolatility() {
double volatilityExpected = NORMAL_SURFACE_SWAPTION_EXP_TENOR.getZValue(SWAPTION_1_P_L.getTimeToExpiry(),
SWAPTION_1_P_L.getMaturityTime());
double volatilityComputedLong = METHOD_SWPT_NORMAL.impliedVolatility(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: implied vol",
volatilityExpected, volatilityComputedLong, TOLERANCE_VOL);
double volatilityComputedShort = METHOD_SWPT_NORMAL.impliedVolatility(SWAPTION_1_P_S, MULTICURVE_NEG_NORMAL);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: implied vol",
volatilityExpected, volatilityComputedShort, TOLERANCE_VOL);
}
@Test
public void presentValue() {
MultipleCurrencyAmount pvComputed = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
final double pvbp = METHOD_SWAP.presentValueBasisPoint(SWAPTION_1_P_L.getUnderlyingSwap(), MULTICURVE);
final double forward = PRDC.visitFixedCouponSwap(SWAPTION_1_P_L.getUnderlyingSwap(), MULTICURVE);
double expiry = SWAPTION_1_P_L.getTimeToExpiry();
double volatility = METHOD_SWPT_NORMAL.impliedVolatility(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
NormalFunctionData normalData = new NormalFunctionData(forward, pvbp, volatility);
EuropeanVanillaOption option = new EuropeanVanillaOption(RATE_1, expiry, SWAPTION_1_P_L.isCall());
NormalPriceFunction normalFunction = new NormalPriceFunction();
Function1D<NormalFunctionData, Double> func = normalFunction.getPriceFunction(option);
double pvExpected = func.evaluate(normalData) * (SWAPTION_1_P_L.isLong() ? 1.0 : -1.0);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvExpected, pvComputed.getAmount(USD), TOLERANCE_PV);
}
@Test
public void presentValueNegativeStrike() {
MultipleCurrencyAmount pvComputed = METHOD_SWPT_NORMAL.presentValue(SWAPTION_2, MULTICURVE_NEG_NORMAL);
final double pvbp = METHOD_SWAP.presentValueBasisPoint(SWAPTION_2.getUnderlyingSwap(), MULTICURVE);
final double forward = PRDC.visitFixedCouponSwap(SWAPTION_2.getUnderlyingSwap(), MULTICURVE);
double expiry = SWAPTION_2.getTimeToExpiry();
double volatility = METHOD_SWPT_NORMAL.impliedVolatility(SWAPTION_2, MULTICURVE_NEG_NORMAL);
NormalFunctionData normalData = new NormalFunctionData(forward, pvbp, volatility);
EuropeanVanillaOption option = new EuropeanVanillaOption(RATE_2, expiry, SWAPTION_2.isCall());
NormalPriceFunction normalFunction = new NormalPriceFunction();
Function1D<NormalFunctionData, Double> func = normalFunction.getPriceFunction(option);
double pvExpected = func.evaluate(normalData) * (SWAPTION_2.isLong() ? 1.0 : -1.0);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvExpected, pvComputed.getAmount(USD), TOLERANCE_PV);
}
@Test
public void presentValueLongShortParity() {
MultipleCurrencyAmount pvComputedLong = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyAmount pvComputedShort = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_S, MULTICURVE_NEG_NORMAL);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvComputedLong.getAmount(USD), -pvComputedShort.getAmount(USD), TOLERANCE_PV);
}
@Test
public void presentValuePayerReceiverParity() {
MultipleCurrencyAmount pvComputedPayerLong = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyAmount pvComputedReceShort = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_R_S, MULTICURVE_NEG_NORMAL);
MultipleCurrencyAmount pvSwapPayer = SWAPTION_1_P_L.getUnderlyingSwap().accept(PVDC, MULTICURVE);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvComputedPayerLong.getAmount(USD) + pvComputedReceShort.getAmount(USD),
pvSwapPayer.getAmount(USD), TOLERANCE_PV);
}
@Test
public void presentValueMethodVsCalculator() {
MultipleCurrencyAmount pvMethod = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyAmount pvMCalculator = SWAPTION_1_P_L.accept(PVNSC, MULTICURVE_NEG_NORMAL);
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvMethod.getAmount(USD), pvMCalculator.getAmount(USD), TOLERANCE_PV);
}
@Test
public void presentValueCurveSensitivity() {
MultipleCurrencyParameterSensitivity pvcsAD = PS.calculateSensitivity(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyParameterSensitivity pvcsFD = PS_FD.calculateSensitivity(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
AssertSensitivityObjects.assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethodTest: CurveSensitivity ",
pvcsAD, pvcsFD, TOLERANCE_PV_DELTA);
}
@Test
public void presentValueVolatilitySensitivity() {
double shiftVol = 1.0E-6;
InterpolatedDoublesSurface volShifted = NormalDataSets.normalSurfaceSwaptionExpiryTenor(shiftVol);
NormalSwaptionExpiryTenorProvider multicurveVolShifted =
new NormalSwaptionExpiryTenorProvider(MULTICURVE, volShifted, USD6MLIBOR3M);
MultipleCurrencyAmount pv0 = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyAmount pvS = METHOD_SWPT_NORMAL.presentValue(SWAPTION_1_P_L, multicurveVolShifted);
double pvvsExpected = (pvS.getAmount(USD) - pv0.getAmount(USD)) / shiftVol;
PresentValueSwaptionSurfaceSensitivity pvvsSurface =
METHOD_SWPT_NORMAL.presentValueVolatilitySensitivity(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
double pvvsComputed = pvvsSurface.getSensitivity().toSingleValue();
assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethod: present value",
pvvsExpected, pvvsComputed, TOLERANCE_PV_DELTA);
}
@Test
public void presentValueCurveSensitivityMethodVsCalculator() {
MultipleCurrencyMulticurveSensitivity pvcsMethod =
METHOD_SWPT_NORMAL.presentValueCurveSensitivity(SWAPTION_1_P_L, MULTICURVE_NEG_NORMAL);
MultipleCurrencyMulticurveSensitivity pvcsCalculator = SWAPTION_1_P_L.accept(PVCSNSC, MULTICURVE_NEG_NORMAL);
AssertSensitivityObjects.assertEquals("SwaptionPhysicalFixedIborNormalExpiryTenorMethodTest: CurveSensitivity ",
pvcsMethod, pvcsCalculator, TOLERANCE_PV_DELTA);
}
static SwapFixedIborDefinition swap(final LocalDate effectiveDate, final LocalDate maturityDate, final double rate,
final double notional, final boolean payer) {
NotionalProvider notionalProvider = new NotionalProvider() {
@Override
public double getAmount(final LocalDate date) {
return notional;
}
};
AnnuityDefinition<?> fixedGeneric = new FixedAnnuityDefinitionBuilder().
payer(payer).currency(USD6MLIBOR3M.getCurrency()).notional(notionalProvider).startDate(effectiveDate).
endDate(maturityDate).dayCount(USD6MLIBOR3M.getFixedLegDayCount()).
accrualPeriodFrequency(USD6MLIBOR3M.getFixedLegPeriod()).rate(rate).
accrualPeriodParameters(ADJUSTED_DATE_LIBOR).build();
AnnuityCouponFixedDefinition fixegLeg =
new AnnuityCouponFixedDefinition((CouponFixedDefinition[])fixedGeneric.getPayments(), NYC);
/** Ibor leg */
AnnuityDefinition<? extends CouponDefinition> iborGeneric =
(AnnuityDefinition<? extends CouponDefinition>)
new FloatingAnnuityDefinitionBuilder().payer(!payer).notional(notionalProvider).startDate(effectiveDate).
endDate(maturityDate).index(USDLIBOR3M).accrualPeriodFrequency(USDLIBOR3M.getTenor()).
rollDateAdjuster(RollConvention.NONE.getRollDateAdjuster(0)).
resetDateAdjustmentParameters(ADJUSTED_DATE_LIBOR).accrualPeriodParameters(ADJUSTED_DATE_LIBOR).
dayCount(USDLIBOR3M.getDayCount()).fixingDateAdjustmentParameters(OFFSET_ADJ_LIBOR).
currency(USDLIBOR3M.getCurrency()).build();
AnnuityCouponIborDefinition iborLeg =
new AnnuityCouponIborDefinition((CouponIborDefinition[]) iborGeneric.getPayments(), USDLIBOR3M, NYC);
SwapFixedIborDefinition irs = new SwapFixedIborDefinition(fixegLeg, iborLeg);
return irs;
}
}