/**
* Copyright (C) 2011 - 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.Period;
import org.threeten.bp.ZonedDateTime;
import cern.jet.random.engine.MersenneTwister;
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.index.IndexSwap;
import com.opengamma.analytics.financial.instrument.swap.SwapFixedIborDefinition;
import com.opengamma.analytics.financial.instrument.swaption.SwaptionPhysicalFixedIborDefinition;
import com.opengamma.analytics.financial.interestrate.payments.derivative.Coupon;
import com.opengamma.analytics.financial.interestrate.swap.derivative.SwapFixedCoupon;
import com.opengamma.analytics.financial.interestrate.swap.provider.SwapFixedCouponDiscountingMethod;
import com.opengamma.analytics.financial.interestrate.swaption.derivative.SwaptionPhysicalFixedIbor;
import com.opengamma.analytics.financial.model.interestrate.TestsDataSetG2pp;
import com.opengamma.analytics.financial.model.interestrate.definition.G2ppPiecewiseConstantParameters;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.NormalFunctionData;
import com.opengamma.analytics.financial.model.volatility.NormalImpliedVolatilityFormula;
import com.opengamma.analytics.financial.montecarlo.provider.G2ppMonteCarloMethod;
import com.opengamma.analytics.financial.provider.calculator.discounting.ParRateDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator;
import com.opengamma.analytics.financial.provider.description.MulticurveProviderDiscountDataSets;
import com.opengamma.analytics.financial.provider.description.interestrate.G2ppProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.schedule.ScheduleCalculator;
import com.opengamma.analytics.math.random.NormalRandomNumberGenerator;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.daycount.DayCount;
import com.opengamma.financial.convention.daycount.DayCountFactory;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.time.DateUtils;
/**
* Tests related to the pricing of physical delivery swaption in G2++ model.
*/
public class SwaptionPhysicalFixedIborG2ppMethodTest {
private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2011, 7, 7);
private static final MulticurveProviderDiscount MULTICURVES = MulticurveProviderDiscountDataSets.createMulticurveEurUsd();
private static final IborIndex IBOR_INDEX = MulticurveProviderDiscountDataSets.getIndexesIborMulticurveEurUsd()[0];
private static final Currency CUR = IBOR_INDEX.getCurrency();
private static final Calendar CALENDAR = MulticurveProviderDiscountDataSets.getEURCalendar();
private static final GeneratorSwapFixedIborMaster GENERATOR_SWAP_MASTER = GeneratorSwapFixedIborMaster.getInstance();
private static final int SPOT_LAG = IBOR_INDEX.getSpotLag();
private static final int SWAP_TENOR_YEAR = 5;
private static final Period SWAP_TENOR = Period.ofYears(SWAP_TENOR_YEAR);
private static final Period FIXED_PAYMENT_PERIOD = Period.ofMonths(6);
private static final DayCount FIXED_DAY_COUNT = DayCountFactory.INSTANCE.getDayCount("30/360");
private static final IndexSwap SWAP_INDEX = new IndexSwap(FIXED_PAYMENT_PERIOD, FIXED_DAY_COUNT, IBOR_INDEX, SWAP_TENOR, CALENDAR);
private static final ZonedDateTime EXPIRY_DATE = DateUtils.getUTCDate(2016, 7, 7);
private static final boolean IS_LONG = true;
private static final ZonedDateTime SETTLEMENT_DATE = ScheduleCalculator.getAdjustedDate(EXPIRY_DATE, SPOT_LAG, CALENDAR);
private static final double NOTIONAL = 100000000; //100m
private static final double RATE = 0.0225;
private static final boolean FIXED_IS_PAYER = true;
private static final SwapFixedIborDefinition SWAP_PAYER_DEFINITION = SwapFixedIborDefinition.from(SETTLEMENT_DATE, SWAP_INDEX, NOTIONAL, RATE, FIXED_IS_PAYER, CALENDAR);
private static final SwapFixedIborDefinition SWAP_RECEIVER_DEFINITION = SwapFixedIborDefinition.from(SETTLEMENT_DATE, SWAP_INDEX, NOTIONAL, RATE, !FIXED_IS_PAYER, CALENDAR);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_LONG_PAYER_DEFINITION = SwaptionPhysicalFixedIborDefinition.from(EXPIRY_DATE, SWAP_PAYER_DEFINITION, IS_LONG);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_LONG_RECEIVER_DEFINITION = SwaptionPhysicalFixedIborDefinition.from(EXPIRY_DATE, SWAP_RECEIVER_DEFINITION, IS_LONG);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_SHORT_PAYER_DEFINITION = SwaptionPhysicalFixedIborDefinition.from(EXPIRY_DATE, SWAP_PAYER_DEFINITION, !IS_LONG);
private static final SwaptionPhysicalFixedIborDefinition SWAPTION_SHORT_RECEIVER_DEFINITION = SwaptionPhysicalFixedIborDefinition.from(EXPIRY_DATE, SWAP_RECEIVER_DEFINITION, !IS_LONG);
//to derivatives
private static final SwapFixedCoupon<Coupon> SWAP_RECEIVER = SWAP_RECEIVER_DEFINITION.toDerivative(REFERENCE_DATE);
private static final SwaptionPhysicalFixedIbor SWAPTION_LONG_PAYER = SWAPTION_LONG_PAYER_DEFINITION.toDerivative(REFERENCE_DATE);
private static final SwaptionPhysicalFixedIbor SWAPTION_LONG_RECEIVER = SWAPTION_LONG_RECEIVER_DEFINITION.toDerivative(REFERENCE_DATE);
private static final SwaptionPhysicalFixedIbor SWAPTION_SHORT_PAYER = SWAPTION_SHORT_PAYER_DEFINITION.toDerivative(REFERENCE_DATE);
private static final SwaptionPhysicalFixedIbor SWAPTION_SHORT_RECEIVER = SWAPTION_SHORT_RECEIVER_DEFINITION.toDerivative(REFERENCE_DATE);
private static final G2ppPiecewiseConstantParameters PARAMETERS_G2PP = TestsDataSetG2pp.createG2ppParameters1();
private static final G2ppProviderDiscount G2PP_MULTICURVES = new G2ppProviderDiscount(MULTICURVES, PARAMETERS_G2PP, CUR);
private static final SwaptionPhysicalFixedIborG2ppApproximationMethod METHOD_G2PP_APPROXIMATION = SwaptionPhysicalFixedIborG2ppApproximationMethod.getInstance();
private static final SwaptionPhysicalFixedIborG2ppNumericalIntegrationMethod METHOD_G2PP_NI = new SwaptionPhysicalFixedIborG2ppNumericalIntegrationMethod();
private static final SwapFixedCouponDiscountingMethod METHOD_SWAP = SwapFixedCouponDiscountingMethod.getInstance();
private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance();
private static final ParRateDiscountingCalculator PRDC = ParRateDiscountingCalculator.getInstance();
private static final double BP1 = 0.0001;
private static final double TOLERANCE_PV = 1.0E-2;
// @Test(enabled = false)
// /**
// * Test the present value vs a external system. "enabled = false" for the standard testing: the external system is using a TimeCalculator with ACT/365.
// */
// public void presentValueExternal() {
// G2ppPiecewiseConstantParameters parametersCst = TestsDataSetG2pp.createG2ppCstParameters();
// final YieldAndDiscountCurve curve5 = YieldCurve.from(ConstantDoublesCurve.from(0.05));
// final YieldCurveBundle curves = new YieldCurveBundle();
// curves.setCurve(FUNDING_CURVE_NAME, curve5);
// curves.setCurve(FORWARD_CURVE_NAME, curve5);
// G2ppPiecewiseConstantDataBundle bundleCst = new G2ppPiecewiseConstantDataBundle(parametersCst, curves);
// CurrencyAmount pv = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_PAYER_LONG, bundleCst);
// double pvExternal = 6885626.28245924; // ! TimeCalculator with ACT/365
// assertEquals("Swaption physical - G2++ - present value - external system", pvExternal, pv.getAmount(), 1E-2);
// }
@Test(enabled = true)
/**
* Test the present value vs a hard-coded value.
*/
public void presentValue() {
final MultipleCurrencyAmount pv = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
final double pvExpected = 4276532.681;
assertEquals("Swaption physical - G2++ - present value - hard coded value", pvExpected, pv.getAmount(CUR), TOLERANCE_PV);
}
@Test
/**
* Tests long/short parity.
*/
public void longShortParity() {
final MultipleCurrencyAmount pvPayerLong = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvPayerShort = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_SHORT_PAYER, G2PP_MULTICURVES);
assertEquals("Swaption physical - G2++ - present value - long/short parity", pvPayerLong.getAmount(CUR), -pvPayerShort.getAmount(CUR), TOLERANCE_PV);
final MultipleCurrencyAmount pvReceiverLong = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_RECEIVER, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvReceiverShort = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_SHORT_RECEIVER, G2PP_MULTICURVES);
assertEquals("Swaption physical - G2++ - present value - long/short parity", pvReceiverLong.getAmount(CUR), -pvReceiverShort.getAmount(CUR), TOLERANCE_PV);
}
@Test
/**
* Tests payer/receiver/swap parity.
*/
public void payerReceiverParity() {
final MultipleCurrencyAmount pvReceiverLong = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_RECEIVER, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvPayerShort = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_SHORT_PAYER, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvSwap = SWAP_RECEIVER.accept(PVDC, MULTICURVES);
assertEquals("Swaption physical - G2++ - present value - payer/receiver/swap parity", pvReceiverLong.getAmount(CUR) + pvPayerShort.getAmount(CUR), pvSwap.getAmount(CUR), TOLERANCE_PV);
}
@Test
/**
* Test the present value by approximation vs by numerical integration.
*/
public void approximationNumericalIntegration() {
final MultipleCurrencyAmount pvApproximation = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvNI = METHOD_G2PP_NI.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
assertEquals("Swaption physical - G2++ - present value - approximation vs Numerical integration", pvApproximation.getAmount(CUR), pvNI.getAmount(CUR), 2.0E+3);
}
@Test
/**
* Test the present value by approximation vs Monte Carlo.
*/
public void presentValueMonteCarlo() {
final int nbPath = 12500;
final G2ppMonteCarloMethod methodMC = new G2ppMonteCarloMethod(new NormalRandomNumberGenerator(0.0, 1.0, new MersenneTwister()), nbPath);
final MultipleCurrencyAmount pvMC = methodMC.presentValue(SWAPTION_LONG_PAYER, CUR, G2PP_MULTICURVES);
final MultipleCurrencyAmount pvApproximation = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
assertEquals("Swaption physical - G2++ - present value - approximation vs Monte Carlo", pvApproximation.getAmount(CUR), pvMC.getAmount(CUR), 2.5E+4);
}
@Test(enabled = false)
/**
* Test the present value by approximation vs Monte Carlo: convergence.
*/
public void presentValueMonteCarloConvergence() {
final MultipleCurrencyAmount pvApproximation = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
final int[] nbPath = new int[] {12500, 100000, 1000000, 10000000};
final MultipleCurrencyAmount[] pvMC = new MultipleCurrencyAmount[nbPath.length];
final double[] pvDiff = new double[nbPath.length];
for (int loopmc = 0; loopmc < nbPath.length; loopmc++) {
final G2ppMonteCarloMethod methodMC = new G2ppMonteCarloMethod(new NormalRandomNumberGenerator(0.0, 1.0, new MersenneTwister()), nbPath[loopmc]);
pvMC[loopmc] = methodMC.presentValue(SWAPTION_LONG_PAYER, CUR, G2PP_MULTICURVES);
pvDiff[loopmc] = pvApproximation.getAmount(CUR) - pvMC[loopmc].getAmount(CUR);
}
assertEquals("Swaption physical - G2++ - present value - approximation vs Monte Carlo", pvApproximation.getAmount(CUR), pvMC[nbPath.length - 1].getAmount(CUR), 1.0E+3);
}
@Test(enabled = true)
/**
* Test the present value by approximation vs by numerical integration for a grid of expiry/tenor.
* To check the precision, increase the NB_INTEGRATION to 50 (slower but more precise).
*/
public void approximationNumericalIntegrationGrid() {
final G2ppPiecewiseConstantParameters parametersG2pp = TestsDataSetG2pp.createG2ppParameters3();
final G2ppProviderDiscount bundleG2pp = new G2ppProviderDiscount(MULTICURVES, parametersG2pp, CUR);
final GeneratorSwapFixedIbor generator = GENERATOR_SWAP_MASTER.getGenerator("EUR1YEURIBOR6M", CALENDAR);
final Period[] expiry = new Period[] {Period.ofMonths(6), Period.ofYears(1), Period.ofYears(2), Period.ofYears(5), Period.ofYears(10), Period.ofYears(25)};
final int nbExpiry = expiry.length;
final Period[] tenor = new Period[] {Period.ofYears(2), Period.ofYears(5), Period.ofYears(10), Period.ofYears(25)};
final int nbTenor = tenor.length;
final double[] fixedRate = new double[] {0.01, 0.02, 0.025, 0.03, 0.04};
final int nbStrike = fixedRate.length;
final SwaptionPhysicalFixedIbor[][][] swaption = new SwaptionPhysicalFixedIbor[nbExpiry][nbTenor][nbStrike];
final ZonedDateTime[] expiryDate = new ZonedDateTime[nbExpiry];
final ZonedDateTime[] settleDate = new ZonedDateTime[nbExpiry];
for (int loopexp = 0; loopexp < nbExpiry; loopexp++) {
expiryDate[loopexp] = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, expiry[loopexp], generator.getIborIndex(), CALENDAR);
settleDate[loopexp] = ScheduleCalculator.getAdjustedDate(expiryDate[loopexp], generator.getSpotLag(), CALENDAR);
for (int loopten = 0; loopten < nbTenor; loopten++) {
for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) {
final SwapFixedIborDefinition swapDefinition = SwapFixedIborDefinition.from(settleDate[loopexp], tenor[loopten], generator, 10000.0, fixedRate[loopstrike], true);
final SwaptionPhysicalFixedIborDefinition swaptionDefinition = SwaptionPhysicalFixedIborDefinition.from(expiryDate[loopexp], swapDefinition, true);
swaption[loopexp][loopten][loopstrike] = swaptionDefinition.toDerivative(REFERENCE_DATE);
}
}
}
final double[][][] pvApprox = new double[nbExpiry][nbTenor][nbStrike];
final double[][][] volApprox = new double[nbExpiry][nbTenor][nbStrike];
final double[][][] pvNI = new double[nbExpiry][nbTenor][nbStrike];
final double[][][] volNI = new double[nbExpiry][nbTenor][nbStrike];
final double[][][] pvDiff = new double[nbExpiry][nbTenor][nbStrike];
final double[][][] volDiff = new double[nbExpiry][nbTenor][nbStrike];
final NormalImpliedVolatilityFormula implied = new NormalImpliedVolatilityFormula(); // Normal vol: LogNormal vol not always well defined with G2++ prices
for (int loopexp = 0; loopexp < nbExpiry; loopexp++) {
for (int loopten = 0; loopten < nbTenor; loopten++) {
for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) {
pvApprox[loopexp][loopten][loopstrike] = METHOD_G2PP_APPROXIMATION.presentValue(swaption[loopexp][loopten][loopstrike], bundleG2pp).getAmount(CUR);
pvNI[loopexp][loopten][loopstrike] = METHOD_G2PP_NI.presentValue(swaption[loopexp][loopten][loopstrike], bundleG2pp).getAmount(CUR);
pvDiff[loopexp][loopten][loopstrike] = pvApprox[loopexp][loopten][loopstrike] - pvNI[loopexp][loopten][loopstrike];
final double pvbp = METHOD_SWAP.presentValueBasisPoint(swaption[loopexp][loopten][loopstrike].getUnderlyingSwap(), MULTICURVES);
final double forward = swaption[loopexp][loopten][loopstrike].getUnderlyingSwap().accept(PRDC, MULTICURVES);
final NormalFunctionData data = new NormalFunctionData(forward, pvbp, 0.01);
volApprox[loopexp][loopten][loopstrike] = implied.getImpliedVolatility(data, swaption[loopexp][loopten][loopstrike], pvApprox[loopexp][loopten][loopstrike]);
volNI[loopexp][loopten][loopstrike] = implied.getImpliedVolatility(data, swaption[loopexp][loopten][loopstrike], pvNI[loopexp][loopten][loopstrike]);
volDiff[loopexp][loopten][loopstrike] = (volApprox[loopexp][loopten][loopstrike] - volNI[loopexp][loopten][loopstrike]) / BP1; // In bp
}
}
}
for (int loopexp = 0; loopexp < nbExpiry; loopexp++) {
for (int loopten = 0; loopten < nbTenor; loopten++) {
for (int loopstrike = 0; loopstrike < nbStrike; loopstrike++) {
assertEquals("Swaption physical - G2++ - present value - approximation vs Numerical integration - exp: " + loopexp + ", ten: " + loopten + ", str: " + loopstrike,
volNI[loopexp][loopten][loopstrike], volApprox[loopexp][loopten][loopstrike], 3 * BP1);
}
}
}
}
@Test(enabled = false)
/**
* Tests of performance. "enabled = false" for the standard testing.
*/
public void performance() {
long startTime, endTime;
final int nbTest = 100;
MultipleCurrencyAmount pvPayerLongApproximation = MultipleCurrencyAmount.of(CUR, 0.0);
MultipleCurrencyAmount pvPayerLongNI = MultipleCurrencyAmount.of(CUR, 0.0);
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
pvPayerLongApproximation = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " pv swaption G2++ approximation method: " + (endTime - startTime) + " ms");
// Performance note: G2++ price: 20-Nov-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 275 ms for 10000 swaptions.
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
pvPayerLongNI = METHOD_G2PP_NI.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " pv swaption G2++ numerical integration method: " + (endTime - startTime) + " ms");
// Performance note: G2++ price: 20-Nov-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 2200 ms for 100 swaptions. (result strongly dependent of the number of integration points).
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
pvPayerLongApproximation = METHOD_G2PP_APPROXIMATION.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " pv swaption G2++ approximation method: " + (endTime - startTime) + " ms");
// Performance note: G2++ price: 20-Nov-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 275 ms for 10000 swaptions.
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
pvPayerLongNI = METHOD_G2PP_NI.presentValue(SWAPTION_LONG_PAYER, G2PP_MULTICURVES);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " pv swaption G2++ numerical integration method: " + (endTime - startTime) + " ms");
// Performance note: G2++ price: 20-Nov-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 2200 ms for 100 swaptions. (result strongly dependent of the number of integration points).
System.out.println("G2++ approximation - present value: " + pvPayerLongApproximation);
System.out.println("G2++ numerical integration - present value: " + pvPayerLongNI);
}
@Test(enabled = false)
/**
* Tests of performance. "enabled = false" for the standard testing.
*/
public void performanceMC() {
long startTime, endTime;
final int nbTest = 10;
final int nbPath = 12500;
final G2ppMonteCarloMethod methodMC = new G2ppMonteCarloMethod(new NormalRandomNumberGenerator(0.0, 1.0, new MersenneTwister()), nbPath);
@SuppressWarnings("unused")
MultipleCurrencyAmount pvMC;
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
pvMC = methodMC.presentValue(SWAPTION_LONG_PAYER, CUR, G2PP_MULTICURVES);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " pv swaption physical G2++ Monte Carlo with " + nbPath + " paths: " + (endTime - startTime) + " ms");
// Performance note: G2++ price: 04-Dec-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 140 ms for 10 swaptions (12500 paths).
}
}