/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.fxopt;
import static com.opengamma.strata.basics.date.DayCounts.ACT_360;
import static com.opengamma.strata.collect.TestHelper.assertSerialization;
import static com.opengamma.strata.collect.TestHelper.assertThrowsIllegalArg;
import static com.opengamma.strata.collect.TestHelper.coverBeanEquals;
import static com.opengamma.strata.collect.TestHelper.coverImmutableBean;
import static org.testng.AssertJUnit.assertEquals;
import java.util.ArrayList;
import java.util.List;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableList;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.market.curve.interpolator.BoundCurveInterpolator;
import com.opengamma.strata.market.curve.interpolator.CurveExtrapolator;
import com.opengamma.strata.market.curve.interpolator.CurveExtrapolators;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolator;
import com.opengamma.strata.market.curve.interpolator.CurveInterpolators;
/**
* Tests related to the construction of term structure of smile data from delta.
* Tests related to the interpolation of volatility.
*/
@Test
public class InterpolatedStrikeSmileDeltaTermStructureTest {
private static final DoubleArray TIME_TO_EXPIRY = DoubleArray.of(0.10, 0.25, 0.50, 1.00, 2.00, 3.00);
private static final DoubleArray ATM = DoubleArray.of(0.175, 0.185, 0.18, 0.17, 0.16, 0.17);
private static final DoubleArray DELTA = DoubleArray.of(0.10, 0.25);
private static final DoubleMatrix RISK_REVERSAL = DoubleMatrix.copyOf(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 DoubleMatrix STRANGLE = DoubleMatrix.copyOf(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.size();
private static final List<SmileDeltaParameters> VOLATILITY_TERM = new ArrayList<>(NB_EXP);
static {
for (int loopexp = 0; loopexp < NB_EXP; loopexp++) {
VOLATILITY_TERM.add(SmileDeltaParameters.of(
TIME_TO_EXPIRY.get(loopexp),
ATM.get(loopexp),
DELTA,
DoubleArray.copyOf(RISK_REVERSAL.toArray()[loopexp]),
DoubleArray.copyOf(STRANGLE.toArray()[loopexp])));
}
}
private static final CurveInterpolator INTERPOLATOR_STRIKE = CurveInterpolators.LINEAR;
private static final CurveExtrapolator FLAT = CurveExtrapolators.FLAT;
private static final InterpolatedStrikeSmileDeltaTermStructure SMILE_TERM =
InterpolatedStrikeSmileDeltaTermStructure.of(VOLATILITY_TERM, ACT_360, INTERPOLATOR_STRIKE, FLAT, FLAT);
private static final double TOLERANCE_VOL = 1.0E-10;
//-------------------------------------------------------------------------
public void getter() {
assertEquals("Smile by delta term structure: volatility", VOLATILITY_TERM, SMILE_TERM.getVolatilityTerm());
}
//-------------------------------------------------------------------------
public void constructor() {
InterpolatedStrikeSmileDeltaTermStructure smileTerm1 =
InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE, ACT_360);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTerm1);
InterpolatedStrikeSmileDeltaTermStructure smileTerm2 =
InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, STRANGLE, ACT_360, INTERPOLATOR_STRIKE, FLAT, FLAT);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTerm2);
}
public void constructor2() {
double[][] vol = new double[NB_EXP][];
for (int loopexp = 0; loopexp < NB_EXP; loopexp++) {
vol[loopexp] = VOLATILITY_TERM.get(loopexp).getVolatility().toArray();
}
InterpolatedStrikeSmileDeltaTermStructure smileTermVol1 = InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, DoubleMatrix.copyOf(vol), ACT_360);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTermVol1);
InterpolatedStrikeSmileDeltaTermStructure smileTermVol2 = InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, DoubleMatrix.copyOf(vol), ACT_360, INTERPOLATOR_STRIKE, FLAT, FLAT);
assertEquals("Smile by delta term structure: constructor", SMILE_TERM, smileTermVol2);
}
public void testWrongDataSize() {
double[][] vol = new double[NB_EXP][];
for (int loopexp = 0; loopexp < NB_EXP; loopexp++) {
vol[loopexp] = VOLATILITY_TERM.get(loopexp).getVolatility().toArray();
}
DoubleArray timeShort = DoubleArray.of(0.10, 0.25, 0.50, 1.00, 2.00);
DoubleArray deltaLong = DoubleArray.of(0.10, 0.2, 0.25);
DoubleArray delta0 = DoubleArray.of();
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
timeShort, DELTA, DoubleMatrix.copyOf(vol), ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, deltaLong, DoubleMatrix.copyOf(vol), ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, delta0, DoubleMatrix.copyOf(vol), ACT_360));
DoubleMatrix shortMat = DoubleMatrix.copyOf(new double[][] {
{0.0300, 0.0100}, {0.0310, 0.0110}, {0.0320, 0.0120}, {0.0330, 0.0130}, {0.0340, 0.0140}});
DoubleMatrix vec = DoubleMatrix.copyOf(new double[][] {
{0.0300}, {0.0310}, {0.0320}, {0.0330}, {0.0340}, {0.0340}});
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
timeShort, DELTA, ATM, RISK_REVERSAL, STRANGLE, ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, deltaLong, ATM, RISK_REVERSAL, STRANGLE, ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, ATM, shortMat, STRANGLE, ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, shortMat, ACT_360));
assertThrowsIllegalArg(() -> InterpolatedStrikeSmileDeltaTermStructure.of(
TIME_TO_EXPIRY, DELTA, ATM, RISK_REVERSAL, vec, ACT_360));
}
//-------------------------------------------------------------------------
/**
* Tests the volatility at a point of the grid.
*/
public void volatilityAtPoint() {
double forward = 1.40;
double timeToExpiry = 0.50;
double[] strikes = SMILE_TERM.getVolatilityTerm().get(2).strike(forward).toArray();
double volComputed = SMILE_TERM.volatility(timeToExpiry, strikes[1], forward);
double volExpected = SMILE_TERM.getVolatilityTerm().get(2).getVolatility().get(1);
assertEquals("Smile by delta term structure: volatility at a point", volExpected, volComputed, TOLERANCE_VOL);
}
/**
* Tests the interpolation in the strike dimension at a time of the grid.
*/
public void volatilityStrikeInterpolation() {
double forward = 1.40;
double timeToExpiry = 0.50;
double strike = 1.50;
DoubleArray strikes = SMILE_TERM.getVolatilityTerm().get(2).strike(forward);
DoubleArray vol = SMILE_TERM.getVolatilityTerm().get(2).getVolatility();
BoundCurveInterpolator interpolator = CurveInterpolators.LINEAR.bind(strikes, vol);
double volExpected = interpolator.interpolate(strike);
double volComputed = SMILE_TERM.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed,
TOLERANCE_VOL);
}
/**
* Tests the extrapolation below the first expiry.
*/
public void volatilityBelowFirstExpiry() {
double forward = 1.40;
double timeToExpiry = 0.05;
double strike = 1.45;
SmileDeltaParameters smile = SmileDeltaParameters.of(
timeToExpiry,
ATM.get(0),
DELTA,
DoubleArray.copyOf(RISK_REVERSAL.toArray()[0]),
DoubleArray.copyOf(STRANGLE.toArray()[0]));
DoubleArray strikes = smile.strike(forward);
DoubleArray vol = smile.getVolatility();
double volExpected = INTERPOLATOR_STRIKE.bind(strikes, vol, FLAT, FLAT).interpolate(strike);
double volComputed = SMILE_TERM.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed,
TOLERANCE_VOL);
}
/**
* Tests the extrapolation above the last expiry.
*/
public void volatilityAboveLastExpiry() {
double forward = 1.40;
double timeToExpiry = 5.00;
double strike = 1.45;
SmileDeltaParameters smile = SmileDeltaParameters.of(
timeToExpiry,
ATM.toArray()[NB_EXP - 1],
DELTA,
DoubleArray.copyOf(RISK_REVERSAL.toArray()[NB_EXP - 1]),
DoubleArray.copyOf(STRANGLE.toArray()[NB_EXP - 1]));
DoubleArray strikes = smile.strike(forward);
DoubleArray vol = smile.getVolatility();
double volExpected = INTERPOLATOR_STRIKE.bind(strikes, vol, FLAT, FLAT).interpolate(strike);
double volComputed = SMILE_TERM.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed,
TOLERANCE_VOL);
}
/**
* Tests the interpolation in the time and strike dimensions.
*/
public void volatilityTimeInterpolation() {
double forward = 1.40;
double timeToExpiry = 0.75;
double strike = 1.50;
double[] vol050 = SMILE_TERM.getVolatilityTerm().get(2).getVolatility().toArray();
double[] vol100 = SMILE_TERM.getVolatilityTerm().get(3).getVolatility().toArray();
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.get(2) + vol100[loopvol]
* vol100[loopvol] * TIME_TO_EXPIRY.get(3)) / 2.0) / timeToExpiry);
}
SmileDeltaParameters smile = SmileDeltaParameters.of(timeToExpiry, DELTA, DoubleArray.copyOf(vol));
DoubleArray strikes = smile.strike(forward);
double volExpected = INTERPOLATOR_STRIKE.bind(strikes, DoubleArray.copyOf(vol), FLAT, FLAT).interpolate(strike);
double volComputed = SMILE_TERM.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volExpected, volComputed,
TOLERANCE_VOL);
double volTriple = SMILE_TERM.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volComputed, volTriple,
TOLERANCE_VOL);
InterpolatedStrikeSmileDeltaTermStructure smileTerm2 =
InterpolatedStrikeSmileDeltaTermStructure.of(VOLATILITY_TERM, ACT_360);
double volComputed2 = smileTerm2.volatility(timeToExpiry, strike, forward);
assertEquals("Smile by delta term structure: volatility interpolation on strike", volComputed, volComputed2,
TOLERANCE_VOL);
}
/**
* Tests the interpolation and its derivative with respect to the data by comparison to finite difference.
*/
public void volatilityAjoint() {
double forward = 1.40;
double[] timeToExpiry = new double[] {0.75, 1.00, 2.50};
double[] strike = new double[] {1.50, 1.70, 2.20};
double[] tolerance = new double[] {3e-2, 1e-1, 1e-5};
int nbTest = strike.length;
double shift = 0.00001;
for (int looptest = 0; looptest < nbTest; looptest++) {
double vol = SMILE_TERM.volatility(timeToExpiry[looptest], strike[looptest], forward);
double[][] bucketTest = new double[TIME_TO_EXPIRY.size()][2 * DELTA.size() + 1];
VolatilityAndBucketedSensitivities volComputed =
SMILE_TERM.volatilityAndSensitivities(timeToExpiry[looptest], strike[looptest], forward);
DoubleMatrix bucketSensi = volComputed.getSensitivities();
assertEquals("Smile by delta term structure: volatility adjoint", vol, volComputed.getVolatility(), 1.0E-10);
SmileDeltaParameters[] volData = new SmileDeltaParameters[TIME_TO_EXPIRY.size()];
double[] volBumped = new double[2 * DELTA.size() + 1];
for (int loopexp = 0; loopexp < TIME_TO_EXPIRY.size(); loopexp++) {
for (int loopsmile = 0; loopsmile < 2 * DELTA.size() + 1; loopsmile++) {
System.arraycopy(SMILE_TERM.getVolatilityTerm().toArray(), 0, volData, 0, TIME_TO_EXPIRY.size());
System.arraycopy(SMILE_TERM.getVolatilityTerm().get(loopexp).getVolatility().toArray(), 0, volBumped, 0,
2 * DELTA.size() + 1);
volBumped[loopsmile] += shift;
volData[loopexp] = SmileDeltaParameters.of(TIME_TO_EXPIRY.get(loopexp), DELTA, DoubleArray.copyOf(volBumped));
InterpolatedStrikeSmileDeltaTermStructure smileTermBumped =
InterpolatedStrikeSmileDeltaTermStructure.of(ImmutableList.copyOf(volData), ACT_360);
bucketTest[loopexp][loopsmile] = (smileTermBumped.volatility(timeToExpiry[looptest], strike[looptest],
forward) - volComputed.getVolatility()) / shift;
assertEquals("Smile by delta term structure: (test: " + looptest + ") volatility bucket sensitivity "
+ loopexp + " - " + loopsmile, bucketTest[loopexp][loopsmile],
bucketSensi.get(loopexp, loopsmile), tolerance[looptest]);
}
}
}
}
//-------------------------------------------------------------------------
public void coverage() {
coverImmutableBean(SMILE_TERM);
InterpolatedStrikeSmileDeltaTermStructure other = InterpolatedStrikeSmileDeltaTermStructure.of(
DoubleArray.of(0.1, 0.5),
DoubleArray.of(0.25),
DoubleMatrix.copyOf(new double[][] { {0.15, 0.1, 0.12}, {0.1, 0.07, 0.08}}),
ACT_360,
CurveInterpolators.NATURAL_SPLINE,
CurveExtrapolators.LINEAR,
CurveExtrapolators.LINEAR,
CurveInterpolators.NATURAL_SPLINE,
CurveExtrapolators.LINEAR,
CurveExtrapolators.LINEAR);
coverBeanEquals(SMILE_TERM, other);
}
public void test_serialization() {
assertSerialization(SMILE_TERM);
}
}