/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.volatility.surface;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.option.definition.SmileDeltaParameters;
import com.opengamma.analytics.financial.model.volatility.VolatilityAndBucketedSensitivities;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.interpolation.LinearInterpolator1D;
import com.opengamma.analytics.math.interpolation.data.ArrayInterpolator1DDataBundle;
import com.opengamma.util.test.TestGroup;
import com.opengamma.util.tuple.Triple;
/**
* Tests related to the construction of term structure of smile data from delta.
* Tests related to the interpolation of volatility.
*/
@Test(groups = TestGroup.UNIT)
public class SmileDeltaTermStructureParametersStrikeInterpolationTest {
private static final double[] TIME_TO_EXPIRY = {0.10, 0.25, 0.50, 1.00, 2.00, 3.00};
private static final double[] ATM = {0.175, 0.185, 0.18, 0.17, 0.16, 0.17};
private static final double[] DELTA = new double[] {0.10, 0.25};
private static final double[][] RISK_REVERSAL = new double[][] { {-0.010, -0.0050}, {-0.011, -0.0060}, {-0.012, -0.0070}, {-0.013, -0.0080}, {-0.014, -0.0090}, {-0.014, -0.0090}};
private static final double[][] STRANGLE = new double[][] { {0.0300, 0.0100}, {0.0310, 0.0110}, {0.0320, 0.0120}, {0.0330, 0.0130}, {0.0340, 0.0140}, {0.0340, 0.0140}};
private static final int NB_EXP = TIME_TO_EXPIRY.length;
private static final SmileDeltaParameters[] VOLATILITY_TERM = new SmileDeltaParameters[NB_EXP];
static {
for (int loopexp = 0; loopexp < NB_EXP; loopexp++) {
VOLATILITY_TERM[loopexp] = new SmileDeltaParameters(TIME_TO_EXPIRY[loopexp], ATM[loopexp], DELTA, RISK_REVERSAL[loopexp], STRANGLE[loopexp]);
}
}
private static final Interpolator1D INTERPOLATOR_STRIKE = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.LINEAR, Interpolator1DFactory.FLAT_EXTRAPOLATOR,
Interpolator1DFactory.FLAT_EXTRAPOLATOR);
private static final Interpolator1D INTERPOLATOR_TIME = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.TIME_SQUARE, Interpolator1DFactory.FLAT_EXTRAPOLATOR,
Interpolator1DFactory.FLAT_EXTRAPOLATOR);
private static final SmileDeltaTermStructureParametersStrikeInterpolation SMILE_TERM = new SmileDeltaTermStructureParametersStrikeInterpolation(VOLATILITY_TERM, INTERPOLATOR_STRIKE);
private static final double TOLERANCE_VOL = 1.0E-10;
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNullVolatility() {
new SmileDeltaTermStructureParametersStrikeInterpolation(null, INTERPOLATOR_STRIKE);
}
@Test
public void getter() {
assertEquals("Smile by delta term structure: volatility", VOLATILITY_TERM, SMILE_TERM.getVolatilityTerm());
}
@Test
public void constructor() {
final SmileDeltaTermStructureParametersStrikeInterpolation smileTerm2 = new SmileDeltaTermStructureParametersStrikeInterpolation(TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTerm2);
}
@Test
public void constructor2() {
final double[][] vol = new double[NB_EXP][];
for (int loopexp = 0; loopexp < NB_EXP; loopexp++) {
vol[loopexp] = VOLATILITY_TERM[loopexp].getVolatility();
}
final SmileDeltaTermStructureParametersStrikeInterpolation smileTermVol = new SmileDeltaTermStructureParametersStrikeInterpolation(TIME_TO_EXPIRY, DELTA, vol);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTermVol);
}
@Test
/**
* Tests the volatility at a point of the grid.
*/
public void volatilityAtPoint() {
final double forward = 1.40;
final double timeToExpiration = 0.50;
final double[] strikes = SMILE_TERM.getVolatilityTerm()[2].getStrike(forward);
final double volComputed = SMILE_TERM.getVolatility(timeToExpiration, strikes[1], forward);
final double volExpected = SMILE_TERM.getVolatilityTerm()[2].getVolatility()[1];
assertEquals("Smile by delta term structure: volatility at a point", volExpected, volComputed, TOLERANCE_VOL);
}
@Test
/**
* Tests the interpolation in the strike dimension at a time of the grid.
*/
public void volatilityStrikeInterpolation() {
final double forward = 1.40;
final double timeToExpiration = 0.50;
final double strike = 1.50;
final double[] strikes = SMILE_TERM.getVolatilityTerm()[2].getStrike(forward);
final double[] vol = SMILE_TERM.getVolatilityTerm()[2].getVolatility();
final ArrayInterpolator1DDataBundle volatilityInterpolation = new ArrayInterpolator1DDataBundle(strikes, vol);
final LinearInterpolator1D interpolator = new LinearInterpolator1D();
final double volExpected = interpolator.interpolate(volatilityInterpolation, strike);
final double volComputed = SMILE_TERM.getVolatility(timeToExpiration, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed, TOLERANCE_VOL);
}
@Test
/**
* Tests the extrapolation below the first expiration.
*/
public void volatilityBelowFirstExpiry() {
final double forward = 1.40;
final double timeToExpiration = 0.05;
final double strike = 1.45;
final SmileDeltaParameters smile = new SmileDeltaParameters(timeToExpiration, ATM[0], DELTA, RISK_REVERSAL[0], STRANGLE[0]);
final double[] strikes = smile.getStrike(forward);
final double[] vol = smile.getVolatility();
final ArrayInterpolator1DDataBundle volatilityInterpolation = new ArrayInterpolator1DDataBundle(strikes, vol);
final double volExpected = INTERPOLATOR_STRIKE.interpolate(volatilityInterpolation, strike);
final double volComputed = SMILE_TERM.getVolatility(timeToExpiration, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed, TOLERANCE_VOL);
}
@Test
/**
* Tests the extrapolation above the last expiration.
*/
public void volatilityAboveLastExpiry() {
final double forward = 1.40;
final double timeToExpiration = 5.00;
final double strike = 1.45;
final SmileDeltaParameters smile = new SmileDeltaParameters(timeToExpiration, ATM[NB_EXP - 1], DELTA, RISK_REVERSAL[NB_EXP - 1], STRANGLE[NB_EXP - 1]);
final double[] strikes = smile.getStrike(forward);
final double[] vol = smile.getVolatility();
final ArrayInterpolator1DDataBundle volatilityInterpolation = new ArrayInterpolator1DDataBundle(strikes, vol);
final double volExpected = INTERPOLATOR_STRIKE.interpolate(volatilityInterpolation, strike);
final double volComputed = SMILE_TERM.getVolatility(timeToExpiration, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed, TOLERANCE_VOL);
}
@Test
/**
* Tests the interpolation in the time and strike dimensions.
*/
public void volatilityTimeInterpolation() {
final double forward = 1.40;
final double timeToExpiration = 0.75;
final double strike = 1.50;
final double[] vol050 = SMILE_TERM.getVolatilityTerm()[2].getVolatility();
final double[] vol100 = SMILE_TERM.getVolatilityTerm()[3].getVolatility();
final double[] vol = new double[vol050.length];
for (int loopvol = 0; loopvol < vol050.length; loopvol++) {
vol[loopvol] = Math.sqrt(((vol050[loopvol] * vol050[loopvol] * TIME_TO_EXPIRY[2] + vol100[loopvol] * vol100[loopvol] * TIME_TO_EXPIRY[3]) / 2.0) / timeToExpiration);
}
final SmileDeltaParameters smile = new SmileDeltaParameters(timeToExpiration, DELTA, vol);
final double[] strikes = smile.getStrike(forward);
final ArrayInterpolator1DDataBundle volatilityInterpolation = new ArrayInterpolator1DDataBundle(strikes, vol);
final LinearInterpolator1D interpolator = new LinearInterpolator1D();
final double volExpected = interpolator.interpolate(volatilityInterpolation, strike);
final double volComputed = SMILE_TERM.getVolatility(timeToExpiration, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed, TOLERANCE_VOL);
final double volTriple = SMILE_TERM.getVolatility(new Triple<>(timeToExpiration, strike, forward));
assertEquals("Smile by delta term structure: volatility interpolation on strike", volComputed, volTriple, TOLERANCE_VOL);
final SmileDeltaTermStructureParametersStrikeInterpolation smileTerm2 = new SmileDeltaTermStructureParametersStrikeInterpolation(TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE,
INTERPOLATOR_STRIKE, INTERPOLATOR_TIME);
final double volComputed2 = smileTerm2.getVolatility(timeToExpiration, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volComputed, volComputed2, TOLERANCE_VOL);
}
@Test
/**
* Tests the interpolation and its derivative with respect to the data by comparison to finite difference.
*/
public void volatilityAjoint() {
final double forward = 1.40;
final double[] timeToExpiration = new double[] {0.75, 1.00, 2.50};
final double[] strike = new double[] {1.50, 1.70, 2.20};
final double[] tolerance = new double[] {3.0E-2, 1.0E-1, 1.0E-5};
final int nbTest = strike.length;
final double shift = 0.00001;
for (int looptest = 0; looptest < nbTest; looptest++) {
final double vol = SMILE_TERM.getVolatility(timeToExpiration[looptest], strike[looptest], forward);
final double[][] bucketTest = new double[TIME_TO_EXPIRY.length][2 * DELTA.length + 1];
final VolatilityAndBucketedSensitivities volComputed = SMILE_TERM.getVolatilityAndSensitivities(timeToExpiration[looptest], strike[looptest], forward);
final double[][] bucketSensi = volComputed.getBucketedSensitivities();
assertEquals("Smile by delta term structure: volatility adjoint", vol, volComputed.getVolatility(), 1.0E-10);
final SmileDeltaParameters[] volData = new SmileDeltaParameters[TIME_TO_EXPIRY.length];
final double[] volBumped = new double[2 * DELTA.length + 1];
for (int loopexp = 0; loopexp < TIME_TO_EXPIRY.length; loopexp++) {
for (int loopsmile = 0; loopsmile < 2 * DELTA.length + 1; loopsmile++) {
System.arraycopy(SMILE_TERM.getVolatilityTerm(), 0, volData, 0, TIME_TO_EXPIRY.length);
System.arraycopy(SMILE_TERM.getVolatilityTerm()[loopexp].getVolatility(), 0, volBumped, 0, 2 * DELTA.length + 1);
volBumped[loopsmile] += shift;
volData[loopexp] = new SmileDeltaParameters(TIME_TO_EXPIRY[loopexp], DELTA, volBumped);
final SmileDeltaTermStructureParametersStrikeInterpolation smileTermBumped = new SmileDeltaTermStructureParametersStrikeInterpolation(volData, INTERPOLATOR_STRIKE);
bucketTest[loopexp][loopsmile] = (smileTermBumped.getVolatility(timeToExpiration[looptest], strike[looptest], forward) - volComputed.getVolatility()) / shift;
// FIXME: the strike sensitivity to volatility is missing. To be corrected when [PLAT-1396] is fixed.
assertEquals("Smile by delta term structure: (test: " + looptest + ") volatility bucket sensitivity " + loopexp + " - " + loopsmile, bucketTest[loopexp][loopsmile],
bucketSensi[loopexp][loopsmile], tolerance[looptest]);
}
}
}
}
@Test(enabled = false)
/**
* Code to graph the strikes for the given deltas at different expirations. In normal tests, should be (enabled=false).
*/
public void deltaSmile() {
final double forward = 1.40;
final double expiryMax = 2.0;
final int nbExp = 50;
final int nbVol = 2 * DELTA.length + 1;
final double[][] strikes = new double[nbExp][nbVol];
final double[] expiries = new double[nbExp];
final double[] variancePeriodT = new double[nbVol];
final double[] volatilityT = new double[nbVol];
final double[] variancePeriod0 = new double[nbVol];
final double[] variancePeriod1 = new double[nbVol];
for (int loopexp = 0; loopexp < nbExp; loopexp++) {
expiries[loopexp] = loopexp * expiryMax / nbExp;
final ArrayInterpolator1DDataBundle interpData = new ArrayInterpolator1DDataBundle(TIME_TO_EXPIRY, new double[NB_EXP]);
final int indexLower = interpData.getLowerBoundIndex(expiries[loopexp]);
if (expiries[loopexp] < 1.0E-10) {
for (int loopvol = 0; loopvol < nbVol; loopvol++) {
volatilityT[loopvol] = SMILE_TERM.getVolatilityTerm()[indexLower].getVolatility()[loopvol];
}
} else {
final double weight0 = (TIME_TO_EXPIRY[indexLower + 1] - expiries[loopexp]) / (TIME_TO_EXPIRY[indexLower + 1] - TIME_TO_EXPIRY[indexLower]);
// Implementation note: Linear interpolation on variance over the period (s^2*t).
for (int loopvol = 0; loopvol < nbVol; loopvol++) {
variancePeriod0[loopvol] = SMILE_TERM.getVolatilityTerm()[indexLower].getVolatility()[loopvol] * SMILE_TERM.getVolatilityTerm()[indexLower].getVolatility()[loopvol]
* TIME_TO_EXPIRY[indexLower];
variancePeriod1[loopvol] = SMILE_TERM.getVolatilityTerm()[indexLower + 1].getVolatility()[loopvol] * SMILE_TERM.getVolatilityTerm()[indexLower + 1].getVolatility()[loopvol]
* TIME_TO_EXPIRY[indexLower + 1];
variancePeriodT[loopvol] = weight0 * variancePeriod0[loopvol] + (1 - weight0) * variancePeriod1[loopvol];
volatilityT[loopvol] = Math.sqrt(variancePeriodT[loopvol] / expiries[loopexp]);
}
}
final SmileDeltaParameters smile = new SmileDeltaParameters(expiries[loopexp], DELTA, volatilityT);
strikes[loopexp] = smile.getStrike(forward);
}
}
@Test(enabled = false)
/**
* Analysis the code performance. In normal tests, should be (enabled=false).
*/
public void performance() {
long startTime, endTime;
final int nbTest = 100000;
final double forward = 1.40;
final double timeToExpiration = 0.50;
final double strike = 1.50;
@SuppressWarnings("unused")
double volComputed;
startTime = System.currentTimeMillis();
for (int looptest = 0; looptest < nbTest; looptest++) {
volComputed = SMILE_TERM.getVolatility(timeToExpiration, strike, forward);
}
endTime = System.currentTimeMillis();
System.out.println(nbTest + " Smile Delta volatility: " + (endTime - startTime) + " ms");
// Performance note: price: 18-Jun-12: On Mac Pro 3.2 GHz Quad-Core Intel Xeon: 225 ms for 100000 volatilities.
}
}