/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.equity.variance; import static com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory.getInterpolator; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertTrue; import org.testng.annotations.Test; import org.threeten.bp.ZonedDateTime; import com.opengamma.analytics.financial.equity.EquityDerivativeSensitivityCalculator; import com.opengamma.analytics.financial.equity.StaticReplicationDataBundle; import com.opengamma.analytics.financial.equity.variance.pricing.VarianceSwapStaticReplication; import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve; import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceDelta; import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceStrike; import com.opengamma.analytics.financial.varianceswap.VarianceSwap; import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolator; import com.opengamma.analytics.math.interpolation.GridInterpolator2D; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface; import com.opengamma.analytics.math.surface.NodalDoublesSurface; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.util.money.Currency; import com.opengamma.util.test.TestGroup; /** * Test. */ @Test(groups = TestGroup.UNIT) public class VarianceSwapRatesSensitivityTest { private static final EquityDerivativeSensitivityCalculator DELTA_CAL = new EquityDerivativeSensitivityCalculator(VarianceSwapPresentValueCalculator.getInstance()); // Tests ------------------------------------------ /* * List of tests: * EQUITY FWD * - Test zero if vols are flat * Rates * - For two scalar risks, compare to presentValue calc * - calcDiscountRateSensitivity = 10000 * calcPV01 * - For bucketedDelta, compare Sum to calcDiscountRateSensitivity * Vega * - Parallel Vega should be equal to 2 * sigma * Z(0,T) * - Test sum of individuals = One big shift by manually creating another vol surface * - Test that we don't get sensitivity on unexpected expiries */ private static double TOLERATED = 1.0E-8; /** * Forward sensitivity comes only from volatility skew. Let's check * Note the vol surface is flat at 0.5, 1 and 10 years, but skewed at 5 years. */ @Test public void testForwardSensitivity() { final double relShift = 0.01; final double deltaSkew = DELTA_CAL.calcForwardSensitivity(swap5y, MARKET, relShift); final double deltaFlatLong = DELTA_CAL.calcForwardSensitivity(swap10y, MARKET, relShift); final double deltaFlatShort = DELTA_CAL.calcForwardSensitivity(swap1y, MARKET, relShift); assertTrue(Math.abs(deltaSkew) > Math.abs(deltaFlatShort)); assertTrue(Math.abs(deltaSkew) > Math.abs(deltaFlatLong)); assertEquals(deltaFlatLong, deltaFlatShort, TOLERATED); assertEquals(0.0, deltaFlatShort, TOLERATED); } /** * If the smile/skew translates with the forward, we always expect zero forward sensitivity. */ @Test public void testForwardSensitivityForDeltaStrikeParameterisation() { final InterpolatedDoublesSurface DELTA_SURFACE = new InterpolatedDoublesSurface(EXPIRIES, CALLDELTAs, VOLS, new GridInterpolator2D(INTERPOLATOR_1D_LINEAR, INTERPOLATOR_1D_DBLQUAD)); final BlackVolatilitySurfaceDelta DELTA_VOL_SURFACE = new BlackVolatilitySurfaceDelta(DELTA_SURFACE, FORWARD_CURVE); final StaticReplicationDataBundle DELTA_MARKET = new StaticReplicationDataBundle(DELTA_VOL_SURFACE, FUNDING, FORWARD_CURVE); final double relShift = 0.1; final double deltaSkew = DELTA_CAL.calcForwardSensitivity(swap5y, DELTA_MARKET, relShift); final double deltaFlatLong = DELTA_CAL.calcForwardSensitivity(swap10y, DELTA_MARKET, relShift); final double deltaFlatShort = DELTA_CAL.calcForwardSensitivity(swap1y, DELTA_MARKET, relShift); assertEquals(0.0, deltaSkew, TOLERATED); assertEquals(0.0, deltaFlatLong, TOLERATED); assertEquals(0.0, deltaFlatShort, TOLERATED); } @Test public void testTotalRateSensitivity() { final double relShift = 0.01; final double delta = DELTA_CAL.calcForwardSensitivity(swap5y, MARKET, relShift); final double pv = pricer_without_cutoff.presentValue(swap5y, MARKET); final double settlement = swap5y.getTimeToSettlement(); final double totalRateSens = DELTA_CAL.calcDiscountRateSensitivity(swap5y, MARKET, relShift); final double fwd = FORWARD_CURVE.getForward(settlement); assertEquals(totalRateSens, settlement * (delta * fwd - pv), TOLERATED); } @Test public void testDiscountRateSensitivityWithNoSkew() { final double rateSens = DELTA_CAL.calcDiscountRateSensitivity(swap10y, MARKET); final double pv = pricer_without_cutoff.presentValue(swap10y, MARKET); final double settlement = swap10y.getTimeToSettlement(); assertEquals(-settlement * pv, rateSens, TOLERATED); } @Test public void testPV01() { final double rateSens = DELTA_CAL.calcDiscountRateSensitivity(swapStartsNow, MARKET); final double pv01 = DELTA_CAL.calcPV01(swapStartsNow, MARKET); assertEquals(pv01 * 10000, rateSens, TOLERATED); } @Test public void testBucketedDeltaVsPV01() { final double rateSens = DELTA_CAL.calcDiscountRateSensitivity(swapStartsNow, MARKET); final DoubleMatrix1D deltaBuckets = DELTA_CAL.calcDeltaBucketed(swapStartsNow, MARKET); final int nDeltas = deltaBuckets.getNumberOfElements(); final int nYieldNodes = ((YieldCurve) MARKET.getDiscountCurve()).getCurve().size(); assertEquals(nDeltas, nYieldNodes, TOLERATED); double bucketSum = 0.0; for (int i = 0; i < nDeltas; i++) { bucketSum += deltaBuckets.getEntry(i); } assertEquals(rateSens, bucketSum, TOLERATED); } @Test public void testBlackVegaParallel() { final double expiry = 0.5; final double sigma = SURFACE.getZValue(expiry, 100.0); final VarianceSwap swap = new VarianceSwap(tPlusOne, expiry, expiry, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, noObservations, noObsWeights); final double zcb = MARKET.getDiscountCurve().getDiscountFactor(swap.getTimeToSettlement()); final Double vegaParallel = DELTA_CAL.calcBlackVegaParallel(swap, MARKET); final double theoreticalVega = 2 * sigma * zcb * swap.getVarNotional(); assertEquals(theoreticalVega, vegaParallel, 0.01); } @Test public void testBlackVegaForEntireSurface() { // Compute the surface final NodalDoublesSurface vegaSurface = DELTA_CAL.calcBlackVegaForEntireSurface(swapStartsNow, MARKET); // Sum up each constituent final double[] vegaBuckets = vegaSurface.getZDataAsPrimitive(); double sumVegaBuckets = 0.0; for (int i = 0; i < vegaSurface.size(); i++) { sumVegaBuckets += vegaBuckets[i]; } // Compute parallel vega, ie to a true parallel shift final Double parallelVega = DELTA_CAL.calcBlackVegaParallel(swapStartsNow, MARKET); assertEquals(parallelVega, sumVegaBuckets, 0.01); } /** * Test BlackVolatilityDeltaSurface * sum of vega buckets = 4583.92106434809 parallelVega = 4583.95175875458 */ @Test public void testBlackVegaForDeltaSurface() { final InterpolatedDoublesSurface DELTA_SURFACE = new InterpolatedDoublesSurface(EXPIRIES, CALLDELTAs, VOLS, new GridInterpolator2D(INTERPOLATOR_1D_LINEAR, INTERPOLATOR_1D_DBLQUAD)); final BlackVolatilitySurfaceDelta DELTA_VOL_SURFACE = new BlackVolatilitySurfaceDelta(DELTA_SURFACE, FORWARD_CURVE); final StaticReplicationDataBundle DELTA_MARKET = new StaticReplicationDataBundle(DELTA_VOL_SURFACE, FUNDING, FORWARD_CURVE); // Compute the surface final NodalDoublesSurface vegaSurface = DELTA_CAL.calcBlackVegaForEntireSurface(swapStartsNow, DELTA_MARKET); // Sum up each constituent final double[] vegaBuckets = vegaSurface.getZDataAsPrimitive(); double sumVegaBuckets = 0.0; for (int i = 0; i < vegaSurface.size(); i++) { sumVegaBuckets += vegaBuckets[i]; } // Compute parallel vega, ie to a true parallel shift final Double parallelVega = DELTA_CAL.calcBlackVegaParallel(swapStartsNow, DELTA_MARKET); assertEquals(parallelVega, sumVegaBuckets, 0.033); } // Setup ------------------------------------------ // The pricing method // final VarianceSwapStaticReplication pricer_default_w_cutoff = new VarianceSwapStaticReplication(StrikeParameterization.STRIKE); final VarianceSwapStaticReplication pricer_without_cutoff = new VarianceSwapStaticReplication(); // Market data private static final double SPOT = 80; private static final double DRIFT = 0.03; private static final ForwardCurve FORWARD_CURVE = new ForwardCurve(SPOT, DRIFT); // private static final double FORWARD = 100; private static final double[] EXPIRIES = new double[] {0.5, 0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 1.0, 5.0, 5.0, 5.0, 5.0, 10.0, 10.0, 10.0, 10.0 }; private static final double[] STRIKES = new double[] {40, 80, 100, 120, 40, 80, 100, 120, 40, 80, 100, 120, 40, 80, 100, 120 }; private static final double[] CALLDELTAs = new double[] {0.9, 0.75, 0.5, 0.25, 0.9, 0.75, 0.5, 0.25, 0.9, 0.75, 0.5, 0.25, 0.9, 0.75, 0.5, 0.25 }; private static final double[] VOLS = new double[] {0.28, 0.28, 0.28, 0.28, 0.25, 0.25, 0.25, 0.25, 0.26, 0.24, 0.23, 0.25, 0.20, 0.20, 0.20, 0.20 }; private static final CombinedInterpolatorExtrapolator INTERPOLATOR_1D_DBLQUAD = getInterpolator(Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.FLAT_EXTRAPOLATOR, Interpolator1DFactory.FLAT_EXTRAPOLATOR); final static CombinedInterpolatorExtrapolator INTERPOLATOR_1D_LINEAR = getInterpolator(Interpolator1DFactory.LINEAR, Interpolator1DFactory.FLAT_EXTRAPOLATOR, Interpolator1DFactory.FLAT_EXTRAPOLATOR); private static final InterpolatedDoublesSurface SURFACE = new InterpolatedDoublesSurface(EXPIRIES, STRIKES, VOLS, new GridInterpolator2D(INTERPOLATOR_1D_LINEAR, INTERPOLATOR_1D_DBLQUAD)); private static final BlackVolatilitySurfaceStrike VOL_SURFACE = new BlackVolatilitySurfaceStrike(SURFACE); private static double[] maturities = {0.5, 1.0, 5.0, 10.0, 20.0 }; private static double[] rates = {0.02, 0.03, 0.05, 0.05, 0.04 }; private static final YieldCurve FUNDING = YieldCurve.from(new InterpolatedDoublesCurve(maturities, rates, INTERPOLATOR_1D_DBLQUAD, true)); private static final StaticReplicationDataBundle MARKET = new StaticReplicationDataBundle(VOL_SURFACE, FUNDING, FORWARD_CURVE); // The derivative private static final double varStrike = 0.05; private static final double varNotional = 10000; // A notional of 10000 means PV is in bp private static final double now = 0; private static final double expiry1 = 1; private static final double expiry2 = 2; private static final double expiry5 = 5; private static final double expiry10 = 10; private static final int nObsExpected = 750; private static final int noObsDisrupted = 0; private static final double annualization = 252; private static final ZonedDateTime today = ZonedDateTime.now(); private static final ZonedDateTime tomorrow = today.plusDays(1); private static final double tPlusOne = TimeCalculator.getTimeBetween(today, tomorrow); private static final double[] noObservations = {}; private static final double[] noObsWeights = {}; private static final double[] singleObsSoNoReturn = {80 }; private static final VarianceSwap swapStartsNow = new VarianceSwap(now, expiry2, expiry2, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, singleObsSoNoReturn, noObsWeights); //private static final VarianceSwap swapStartsTomorrow = new VarianceSwap(tPlusOne, expiry2, expiry2, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, noObservations, noObsWeights); private static final VarianceSwap swap10y = new VarianceSwap(tPlusOne, expiry10, expiry10, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, noObservations, noObsWeights); private static final VarianceSwap swap5y = new VarianceSwap(tPlusOne, expiry5, expiry5, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, noObservations, noObsWeights); private static final VarianceSwap swap1y = new VarianceSwap(tPlusOne, expiry1, expiry1, varStrike, varNotional, Currency.EUR, annualization, nObsExpected, noObsDisrupted, noObservations, noObsWeights); }