/**
* Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.impl.volatility.local;
import static com.opengamma.strata.market.curve.interpolator.CurveExtrapolators.INTERPOLATOR;
import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.LINEAR;
import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.NATURAL_SPLINE;
import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.NATURAL_SPLINE_NONNEGATIVITY_CUBIC;
import static com.opengamma.strata.market.curve.interpolator.CurveInterpolators.TIME_SQUARE;
import static org.testng.Assert.assertEquals;
import java.util.function.Function;
import org.testng.annotations.Test;
import com.google.common.math.DoubleMath;
import com.opengamma.strata.basics.value.ValueDerivatives;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.tuple.DoublesPair;
import com.opengamma.strata.market.surface.ConstantSurface;
import com.opengamma.strata.market.surface.DefaultSurfaceMetadata;
import com.opengamma.strata.market.surface.DeformedSurface;
import com.opengamma.strata.market.surface.InterpolatedNodalSurface;
import com.opengamma.strata.market.surface.interpolator.GridSurfaceInterpolator;
import com.opengamma.strata.pricer.impl.option.BlackFormulaRepository;
/**
* Test {@link ImpliedTrinomialTreeLocalVolatilityCalculator}.
*/
@Test
public class ImpliedTrinomialTreeLocalVolatilityCalculatorTest {
private static final GridSurfaceInterpolator INTERP_LINEAR = GridSurfaceInterpolator.of(LINEAR, LINEAR);
private static final GridSurfaceInterpolator INTERP_TIMESQ_LINEAR = GridSurfaceInterpolator.of(TIME_SQUARE, LINEAR);
private static final GridSurfaceInterpolator INTERP_CUBIC = GridSurfaceInterpolator.of(
NATURAL_SPLINE, INTERPOLATOR, NATURAL_SPLINE, INTERPOLATOR);
private static final GridSurfaceInterpolator INTERP_CUBIC_NN = GridSurfaceInterpolator.of(
NATURAL_SPLINE_NONNEGATIVITY_CUBIC, INTERPOLATOR, NATURAL_SPLINE_NONNEGATIVITY_CUBIC, INTERPOLATOR);
private static final DoubleArray TIMES =
DoubleArray.of(0.25, 0.25, 0.25, 0.5, 0.5, 0.5, 0.75, 0.75, 0.75, 1, 1, 1);
private static final DoubleArray STRIKES =
DoubleArray.of(0.8, 1.4, 2, 0.8, 1.4, 2, 0.8, 1.4, 2, 0.8, 1.4, 2);
private static final DoubleArray VOLS =
DoubleArray.of(0.21, 0.17, 0.185, 0.17, 0.15, 0.16, 0.15, 0.14, 0.14, 0.14, 0.13, 0.13);
private static final InterpolatedNodalSurface VOL_SURFACE =
InterpolatedNodalSurface.ofUnsorted(DefaultSurfaceMetadata.of("Test"), TIMES, STRIKES, VOLS, INTERP_CUBIC);
private static final DoubleArray PRICES = DoubleArray.of(
0.6024819282312833, 0.0507874597232295, 2.598419834431295E-6, 0.6049279456317715, 0.06581419934686354,
5.691088908182669E-5, 0.607338423139487, 0.07752243330525914, 1.4290312009415014E-4, 0.6097138918063894,
0.0856850744439275, 3.218460178780302E-4);
private static final InterpolatedNodalSurface PRICE_SURFACE =
InterpolatedNodalSurface.ofUnsorted(DefaultSurfaceMetadata.of("Test"), TIMES, STRIKES, PRICES, INTERP_CUBIC_NN);
private static final double SPOT = 1.40;
public void flatVolTest() {
double tol = 2.0e-2;
double constantVol = 0.15;
ConstantSurface impliedVolSurface = ConstantSurface.of("impliedVol", constantVol);
Function<Double, Double> zeroRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.05d;
}
};
Function<Double, Double> zeroRate1 = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.02d;
}
};
ImpliedTrinomialTreeLocalVolatilityCalculator calc =
new ImpliedTrinomialTreeLocalVolatilityCalculator(45, 1d, INTERP_TIMESQ_LINEAR);
InterpolatedNodalSurface localVolSurface =
calc.localVolatilityFromImpliedVolatility(impliedVolSurface, 100d, zeroRate, zeroRate1);
assertEquals(localVolSurface.getZValues().stream().filter(d -> !DoubleMath.fuzzyEquals(d, constantVol, tol)).count(), 0);
}
public void flatVolPriceTest() {
double tol = 2.0e-2;
double constantVol = 0.15;
double spot = 100d;
double maxTime = 1d;
int nSteps = 9;
ConstantSurface impliedVolSurface = ConstantSurface.of("impliedVol", constantVol);
Function<Double, Double> zeroRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0d;
}
};
Function<DoublesPair, ValueDerivatives> func = new Function<DoublesPair, ValueDerivatives>() {
@Override
public ValueDerivatives apply(DoublesPair x) {
double price = BlackFormulaRepository.price(spot, x.getSecond(), x.getFirst(), constantVol, true);
return ValueDerivatives.of(price, DoubleArray.EMPTY);
}
};
DeformedSurface priceSurface = DeformedSurface.of(DefaultSurfaceMetadata.of("price"), impliedVolSurface, func);
ImpliedTrinomialTreeLocalVolatilityCalculator calc =
new ImpliedTrinomialTreeLocalVolatilityCalculator(nSteps, maxTime, INTERP_TIMESQ_LINEAR);
InterpolatedNodalSurface localVolSurface = calc.localVolatilityFromPrice(priceSurface, spot, zeroRate, zeroRate);
assertEquals(localVolSurface.getZValues().stream().filter(d -> !DoubleMath.fuzzyEquals(d, constantVol, tol)).count(), 0);
}
public void comparisonDupireVolTest() {
double tol = 1.0e-2;
ImpliedTrinomialTreeLocalVolatilityCalculator calc =
new ImpliedTrinomialTreeLocalVolatilityCalculator(28, 1.45d, INTERP_LINEAR);
Function<Double, Double> interestRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.03d;
}
};
Function<Double, Double> dividendRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.01d;
}
};
InterpolatedNodalSurface resTri = calc.localVolatilityFromImpliedVolatility(VOL_SURFACE, SPOT, interestRate, dividendRate);
DeformedSurface resDup = (new DupireLocalVolatilityCalculator())
.localVolatilityFromImpliedVolatility(VOL_SURFACE, SPOT, interestRate, dividendRate);
double[][] sampleStrikes = new double[][] {
{0.7 * SPOT, SPOT, 1.1 * SPOT, 1.4 * SPOT, }, {0.5 * SPOT, 0.9 * SPOT, SPOT, 1.3 * SPOT, 1.9 * SPOT } };
double[] sampleTimes = new double[] {0.8, 1.1 };
for (int i = 0; i < sampleTimes.length; ++i) {
double time = sampleTimes[i];
for (double strike : sampleStrikes[i]) {
double volTri = resTri.zValue(time, strike);
double volDup = resDup.zValue(time, strike);
assertEquals(volTri, volDup, tol);
}
}
}
public void comparisonDupirePriceTest() {
double tol = 7.0e-2;
ImpliedTrinomialTreeLocalVolatilityCalculator calc =
new ImpliedTrinomialTreeLocalVolatilityCalculator(22, 1.1d, INTERP_LINEAR);
Function<Double, Double> interestRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.003d;
}
};
Function<Double, Double> dividendRate = new Function<Double, Double>() {
@Override
public Double apply(Double x) {
return 0.01d;
}
};
InterpolatedNodalSurface resTri = calc.localVolatilityFromPrice(PRICE_SURFACE, SPOT, interestRate, dividendRate);
DeformedSurface resDup = (new DupireLocalVolatilityCalculator())
.localVolatilityFromPrice(PRICE_SURFACE, SPOT, interestRate, dividendRate);
// limited range due to interpolation/extrapolation of price surface -> negative call/put price reached
double[][] sampleStrikes = new double[][] { {0.95 * SPOT, 1.05 * SPOT, }, {0.9 * SPOT, SPOT, 1.1 * SPOT } };
double[] sampleTimes = new double[] {0.7, 1.05 };
for (int i = 0; i < sampleTimes.length; ++i) {
double time = sampleTimes[i];
for (double strike : sampleStrikes[i]) {
double volTri = resTri.zValue(time, strike);
double volDup = resDup.zValue(time, strike);
assertEquals(volTri, volDup, tol);
}
}
}
}