/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.interestrate.payments.provider;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.analytics.financial.instrument.payment.CapFloorIborDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponFixedDefinition;
import com.opengamma.analytics.financial.instrument.payment.CouponIborDefinition;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CapFloorIbor;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponFixed;
import com.opengamma.analytics.financial.interestrate.payments.derivative.CouponIbor;
import com.opengamma.analytics.financial.model.option.definition.SABRInterestRateParameters;
import com.opengamma.analytics.financial.provider.calculator.discounting.PresentValueDiscountingCalculator;
import com.opengamma.analytics.financial.provider.calculator.sabrcap.PresentValueSABRCapCalculator;
import com.opengamma.analytics.financial.provider.description.MulticurveProviderDiscountDataSets;
import com.opengamma.analytics.financial.provider.description.SABRDataSets;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.SABRCapProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.SABRCapProviderInterface;
import com.opengamma.analytics.financial.provider.method.CapFloorIborSABRCapMethodInterface;
import com.opengamma.analytics.financial.schedule.ScheduleCalculator;
import com.opengamma.analytics.math.MathException;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.util.money.Currency;
import com.opengamma.util.money.MultipleCurrencyAmount;
import com.opengamma.util.time.DateUtils;
/**
* Tests regarding the pricing of in-arrears Ibor products by replication.
*/
public class CapFloorIborInArrearsReplicationMethodTest {
private static final MulticurveProviderDiscount MULTICURVES = MulticurveProviderDiscountDataSets.createMulticurveEurUsd();
private static final IborIndex EURIBOR6M = MulticurveProviderDiscountDataSets.getIndexesIborMulticurveEurUsd()[1];
private static final Currency EUR = EURIBOR6M.getCurrency();
private static final Calendar CALENDAR = MulticurveProviderDiscountDataSets.getEURCalendar();
private static final SABRInterestRateParameters SABR_PARAMETER = SABRDataSets.createSABR1();
private static final SABRCapProviderDiscount SABR_MULTICURVES = new SABRCapProviderDiscount(MULTICURVES, SABR_PARAMETER, EURIBOR6M);
// Dates
private static final ZonedDateTime REFERENCE_DATE = DateUtils.getUTCDate(2011, 6, 7);
private static final ZonedDateTime START_ACCRUAL_DATE = ScheduleCalculator.getAdjustedDate(REFERENCE_DATE, Period.ofYears(9), EURIBOR6M, CALENDAR);
private static final ZonedDateTime END_ACCRUAL_DATE = ScheduleCalculator.getAdjustedDate(START_ACCRUAL_DATE, EURIBOR6M, CALENDAR);
private static final double ACCRUAL_FACTOR = EURIBOR6M.getDayCount().getDayCountFraction(START_ACCRUAL_DATE, END_ACCRUAL_DATE, CALENDAR);
private static final ZonedDateTime FIXING_DATE = ScheduleCalculator.getAdjustedDate(END_ACCRUAL_DATE, -EURIBOR6M.getSpotLag(), CALENDAR);
private static final double NOTIONAL = 100000000; //100m
private static final double STRIKE = 0.03;
private static final boolean IS_CAP = true;
// Definition description: In arrears
private static final CapFloorIborDefinition CAP_IA_LONG_DEFINITION = new CapFloorIborDefinition(EUR, END_ACCRUAL_DATE, START_ACCRUAL_DATE, END_ACCRUAL_DATE, ACCRUAL_FACTOR, NOTIONAL, FIXING_DATE,
EURIBOR6M, STRIKE, IS_CAP, CALENDAR);
private static final CouponIborDefinition COUPON_IBOR_IA_DEFINITION = new CouponIborDefinition(EUR, END_ACCRUAL_DATE, START_ACCRUAL_DATE, END_ACCRUAL_DATE, ACCRUAL_FACTOR, NOTIONAL, FIXING_DATE,
EURIBOR6M, CALENDAR);
private static final CouponFixedDefinition COUPON_STRIKE_DEFINITION = new CouponFixedDefinition(COUPON_IBOR_IA_DEFINITION, STRIKE);
private static final CapFloorIborDefinition CAP_IA_SHORT_DEFINITION = new CapFloorIborDefinition(EUR, END_ACCRUAL_DATE, START_ACCRUAL_DATE, END_ACCRUAL_DATE, ACCRUAL_FACTOR, -NOTIONAL, FIXING_DATE,
EURIBOR6M, STRIKE, IS_CAP, CALENDAR);
private static final CapFloorIborDefinition FLOOR_IA_SHORT_DEFINITION = new CapFloorIborDefinition(EUR, END_ACCRUAL_DATE, START_ACCRUAL_DATE, END_ACCRUAL_DATE, ACCRUAL_FACTOR, -NOTIONAL,
FIXING_DATE, EURIBOR6M, STRIKE, !IS_CAP, CALENDAR);
// To derivative
private static final CapFloorIbor CAP_LONG = (CapFloorIbor) CAP_IA_LONG_DEFINITION.toDerivative(REFERENCE_DATE);
private static final CouponIbor COUPON_IBOR = (CouponIbor) COUPON_IBOR_IA_DEFINITION.toDerivative(REFERENCE_DATE);
private static final CouponFixed COUPON_STRIKE = COUPON_STRIKE_DEFINITION.toDerivative(REFERENCE_DATE);
private static final CapFloorIbor CAP_SHORT = (CapFloorIbor) CAP_IA_SHORT_DEFINITION.toDerivative(REFERENCE_DATE);
private static final CapFloorIbor FLOOR_SHORT = (CapFloorIbor) FLOOR_IA_SHORT_DEFINITION.toDerivative(REFERENCE_DATE);
// Methods
private static final PresentValueSABRCapCalculator PVSCC = PresentValueSABRCapCalculator.getInstance();
private static final PresentValueDiscountingCalculator PVDC = PresentValueDiscountingCalculator.getInstance();
private static final double CUT_OFF_STRIKE = 0.08;
private static final double MU = 8.00;
private static final CapFloorIborSABRCapExtrapolationRightMethod METHOD_SABREXTRA_STD = new CapFloorIborSABRCapExtrapolationRightMethod(CUT_OFF_STRIKE, MU);
private static final CouponIborInArrearsReplicationMethod METHOD_SABREXTRA_COUPON_IA = new CouponIborInArrearsReplicationMethod(METHOD_SABREXTRA_STD);
private static final CapFloorIborInArrearsSABRCapGenericReplicationMethod METHOD_SABREXTRA_CAP_IA = new CapFloorIborInArrearsSABRCapGenericReplicationMethod(METHOD_SABREXTRA_STD);
private static final double TOLERANCE_PV = 1.0E-0;
// private static final double TOLERANCE_PV_DELTA = 1.0E+2;
@Test
/**
* Tests the present value using the SABR with extrapolation to the right method to price standard cap/floor.
* It is suggested not to use the standard SABR method as it can lead to exploding prices for long term contracts.
*/
public void persentValueSABRExtrapolation() {
final CapFloorIbor capStandard = new CapFloorIbor(EUR, CAP_LONG.getFixingPeriodEndTime(), CAP_LONG.getPaymentYearFraction(), NOTIONAL, CAP_LONG.getFixingTime(), EURIBOR6M,
CAP_LONG.getFixingPeriodStartTime(), CAP_LONG.getFixingPeriodEndTime(), CAP_LONG.getFixingAccrualFactor(), STRIKE, IS_CAP);
final MultipleCurrencyAmount priceStandard = capStandard.accept(PVSCC, SABR_MULTICURVES);
final double forward = MULTICURVES.getForwardRate(CAP_LONG.getIndex(), CAP_LONG.getFixingPeriodStartTime(), CAP_LONG.getFixingPeriodEndTime(), CAP_LONG.getFixingAccrualFactor());
final double beta = (1.0 + CAP_LONG.getFixingAccrualFactor() * forward) * MULTICURVES.getDiscountFactor(EUR, CAP_LONG.getFixingPeriodEndTime())
/ MULTICURVES.getDiscountFactor(EUR, CAP_LONG.getFixingPeriodStartTime());
final double strikePart = (1.0 + CAP_LONG.getFixingAccrualFactor() * STRIKE) * priceStandard.getAmount(EUR);
final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D(1.0, 1E-8, 10);
final InArrearsIntegrant integrant = new InArrearsIntegrant(METHOD_SABREXTRA_STD, capStandard, SABR_MULTICURVES);
double integralPart;
try {
integralPart = integrator.integrate(integrant, STRIKE, STRIKE + 2.0);
} catch (final Exception e) {
throw new MathException(e);
}
integralPart *= 2.0 * CAP_LONG.getFixingAccrualFactor();
final MultipleCurrencyAmount price = METHOD_SABREXTRA_CAP_IA.presentValue(CAP_LONG, SABR_MULTICURVES);
final double priceExpected = (strikePart + integralPart) / beta;
assertEquals("Cap/floor IA - SABR pricing", priceExpected, price.getAmount(EUR), TOLERANCE_PV);
final double priceExpected2 = 203548.836; // From previous run
assertEquals("Cap/floor IA - SABR pricing", priceExpected2, price.getAmount(EUR), TOLERANCE_PV);
}
@Test
/**
* Compare the present value by replication to a value without adjustment.
*/
public void presentValueSABRNoAdjustment() {
final double forward = MULTICURVES.getForwardRate(EURIBOR6M, CAP_LONG.getFixingPeriodStartTime(), CAP_LONG.getFixingPeriodEndTime(), CAP_LONG.getFixingAccrualFactor());
final MultipleCurrencyAmount priceIbor = METHOD_SABREXTRA_COUPON_IA.presentValue(COUPON_IBOR, SABR_MULTICURVES);
assertTrue("Coupon IA - SABR pricing: coupon = cap with strike eps",
priceIbor.getAmount(EUR) > forward * NOTIONAL * CAP_LONG.getPaymentYearFraction() * MULTICURVES.getDiscountFactor(EUR, CAP_LONG.getPaymentTime()));
}
@Test
/**
* Check different parity relationship for the present value: long/short, coupon with cap with strike 0.
*/
public void persentValueSABRExtrapolationParity() {
final MultipleCurrencyAmount priceCapLong = METHOD_SABREXTRA_CAP_IA.presentValue(CAP_LONG, SABR_MULTICURVES);
final MultipleCurrencyAmount priceCapShort = METHOD_SABREXTRA_CAP_IA.presentValue(CAP_SHORT, SABR_MULTICURVES);
assertEquals("Cap/floor IA - SABR pricing: long-short parity", priceCapLong.getAmount(EUR), -priceCapShort.getAmount(EUR), TOLERANCE_PV);
final MultipleCurrencyAmount priceIbor = METHOD_SABREXTRA_COUPON_IA.presentValue(COUPON_IBOR, SABR_MULTICURVES);
final CapFloorIborDefinition cap0Definition = new CapFloorIborDefinition(EUR, END_ACCRUAL_DATE, START_ACCRUAL_DATE, END_ACCRUAL_DATE, ACCRUAL_FACTOR, NOTIONAL, FIXING_DATE, EURIBOR6M, 0.0, IS_CAP, CALENDAR);
final CapFloorIbor cap0 = (CapFloorIbor) cap0Definition.toDerivative(REFERENCE_DATE);
final MultipleCurrencyAmount priceCap0 = METHOD_SABREXTRA_CAP_IA.presentValue(cap0, SABR_MULTICURVES);
assertEquals("Coupon IA - SABR pricing: coupon = cap with strike 0", priceCap0.getAmount(EUR), priceIbor.getAmount(EUR), TOLERANCE_PV);
final MultipleCurrencyAmount priceFloorShort = METHOD_SABREXTRA_CAP_IA.presentValue(FLOOR_SHORT, SABR_MULTICURVES);
final MultipleCurrencyAmount priceStrike = COUPON_STRIKE.accept(PVDC, MULTICURVES);
assertEquals("Cap/floor IA - SABR pricing: cap/floor parity", priceIbor.getAmount(EUR) - priceStrike.getAmount(EUR), priceCapLong.getAmount(EUR) + priceFloorShort.getAmount(EUR), 5.0E+4);
//TODO: check further the difference (numerical?)
}
@Test(enabled = false)
/**
* Performance test. "enabled = false" for the standard testing.
*/
public void sabrExtrapolationPerformance() {
long startTime, endTime;
final int nbTest = 100;
final double[] prices = new double[nbTest];
@SuppressWarnings("unused")
double sum = 0.0;
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
final CapFloorIborSABRCapExtrapolationRightMethod methodSABRExtraStd = new CapFloorIborSABRCapExtrapolationRightMethod(CUT_OFF_STRIKE, MU); //To start with a "clean" method
final CapFloorIborInArrearsSABRCapGenericReplicationMethod methodSABRExtraIA = new CapFloorIborInArrearsSABRCapGenericReplicationMethod(methodSABRExtraStd);
prices[looptest] = methodSABRExtraIA.presentValue(CAP_LONG, SABR_MULTICURVES).getAmount(EUR);
sum += prices[looptest];
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " IA cap by replication (price): " + (endTime - startTime) + " ms");
// Performance note: price: 07-Jun-11: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 385 ms for 100 cap 9Y.
// TODO: review performance: the SABR extrapolation calibration is done at each integration step: result could be stored.
}
/**
* Inner class to implement the integration used in price replication.
*/
private static class InArrearsIntegrant extends Function1D<Double, Double> {
/**
* The base method for the pricing of standard cap/floors.
*/
private final CapFloorIborSABRCapMethodInterface _baseMethod;
/**
* The standard cap/floor used for replication.
*/
private final CapFloorIbor _capStandard;
/**
* The SABR data bundle used in the standard cap/floor pricing.
*/
private final SABRCapProviderInterface _sabrData;
/**
* Constructor with the required data.
* @param baseMethod The base method for the pricing of standard cap/floors.
* @param capStandard The standard cap/floor used for replication.
* @param sabrData The SABR data bundle used in the standard cap/floor pricing.
*/
public InArrearsIntegrant(final CapFloorIborSABRCapMethodInterface baseMethod, final CapFloorIbor capStandard, final SABRCapProviderInterface sabr) {
this._baseMethod = baseMethod;
this._capStandard = capStandard;
this._sabrData = sabr;
}
@SuppressWarnings("synthetic-access")
@Override
public Double evaluate(final Double x) {
final CapFloorIbor capStrike = _capStandard.withStrike(x);
return _baseMethod.presentValue(capStrike, _sabrData).getAmount(EUR);
}
}
}