/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.interestrate.capletstripping;
import static org.testng.AssertJUnit.assertEquals;
import java.util.ArrayList;
import java.util.List;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunction;
import com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunctionProvider;
import com.opengamma.analytics.financial.model.volatility.surface.ParameterizedVolatilitySurfaceProvider;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurfaceProvider;
import com.opengamma.analytics.math.differentiation.VectorFieldFirstOrderDifferentiator;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.function.ParameterizedSurface;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.util.test.TestGroup;
import com.opengamma.util.tuple.DoublesPair;
/**
* Fit a flat (caplet) volatility surface to the cap market values. Clearly, having only one parameter, this will not be
* a good fit in terms of recovering the market values, however this does test a lot of the functionality of the caplet
* stripper.
*/
@Test(groups = TestGroup.UNIT)
public class FlatSurfaceFitTest extends CapletStrippingSetup {
private final static DiscreteVolatilityFunctionProvider DISCRETE_FLAT_SURFACE;
private final static VolatilitySurfaceProvider FLAT_SURFACE;
static {
DISCRETE_FLAT_SURFACE = new DiscreteVolatilityFunctionProvider() {
@Override
public DiscreteVolatilityFunction from(final DoublesPair[] expiryStrikePoints) {
final int size = expiryStrikePoints.length;
final DoubleMatrix2D one = new DoubleMatrix2D(size, 1);
for (int i = 0; i < size; i++) {
one.getData()[i][0] = 1.0;
}
return new DiscreteVolatilityFunction() {
@Override
public DoubleMatrix2D calculateJacobian(final DoubleMatrix1D x) {
return one;
}
@Override
public DoubleMatrix1D evaluate(final DoubleMatrix1D x) {
return new DoubleMatrix1D(size, x.getEntry(0));
}
@Override
public int getLengthOfDomain() {
return 1;
}
@Override
public int getLengthOfRange() {
return size;
}
};
}
};
ParameterizedSurface fs = new ParameterizedSurface() {
@Override
public int getNumberOfParameters() {
return 1;
}
@Override
public Double evaluate(DoublesPair x, DoubleMatrix1D parameters) {
return parameters.getEntry(0);
}
};
FLAT_SURFACE = new ParameterizedVolatilitySurfaceProvider(fs);
}
@Test
public void solveSinglePrice() {
double vol = 0.5;
CapFloor cap = getATMCaps().get(0);
List<CapFloor> caps = new ArrayList<>();
caps.add(cap);
MultiCapFloorPricer pricer = new MultiCapFloorPricer(caps, getYieldCurves());
double price = pricer.price(new double[] {vol })[0];
final CapletStrippingCore imp = new CapletStrippingCore(pricer, FLAT_SURFACE);
CapletStrippingResult res = imp.rootFindForCapPrices(new double[] {price }, new DoubleMatrix1D(1, 0.3));
DoubleMatrix1D fitParms = res.getFitParameters();
assertEquals(1, fitParms.getNumberOfElements());
assertEquals(0.5, fitParms.getEntry(0), 1e-8);
}
@Test
public void priceFitTest() {
final MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
final CapletStrippingCore imp = new CapletStrippingCore(pricer, DISCRETE_FLAT_SURFACE);
final CapletStrippingResult res = imp.leastSqrSolveForCapPrices(getAllCapPrices(), new DoubleMatrix1D(0.4));
// since this is an unbiased LS fit to price it is skewed to fitting the long (10 year caps)
assertEquals(0.3654148224492559, res.getFitParameters().getEntry(0), 1e-15);
}
@Test
public void priceVegaFitTest() {
final MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
final CapletStrippingCore imp = new CapletStrippingCore(pricer, DISCRETE_FLAT_SURFACE);
final double[] capVols = getAllCapVols();
final double[] vega = pricer.vega(capVols);
final double[] prices = pricer.price(capVols);
final CapletStrippingResult res = imp.solveForCapPrices(prices, vega, new DoubleMatrix1D(0.4));
// since this is a vega weighted LS fit to price it looks more like the average cap volatility
assertEquals(0.49945534507287803, res.getFitParameters().getEntry(0), 1e-15);
}
@Test
public void volFitTest() {
final double[] vols = getAllCapVols();
final int n = vols.length;
double sum = 0;
for (int i = 0; i < n; i++) {
sum += vols[i];
}
sum /= n;
final MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
final CapletStrippingCore imp = new CapletStrippingCore(pricer, DISCRETE_FLAT_SURFACE);
final CapletStrippingResult res = imp.leastSqrSolveForCapVols(vols, new DoubleMatrix1D(0.4));
// since this is an unbiased LS fit to cap volatilities, the fit is very close to the average cap volatility
assertEquals(sum, res.getFitParameters().getEntry(0), 1e-7);
assertEquals(0.6051342199655784, res.getFitParameters().getEntry(0), 1e-8);
}
/**
* This tests the cap-vol Jacobian function
*/
@Test
public void functionTest() {
final double vol = 0.4;
final MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
final CapletStrippingCore imp = new CapletStrippingCore(pricer, DISCRETE_FLAT_SURFACE);
final Function1D<DoubleMatrix1D, DoubleMatrix1D> func = imp.getCapVolFunction();
final VectorFieldFirstOrderDifferentiator diff = new VectorFieldFirstOrderDifferentiator();
final Function1D<DoubleMatrix1D, DoubleMatrix2D> jacFDFunc = diff.differentiate(func);
final Function1D<DoubleMatrix1D, DoubleMatrix2D> jacFunc = imp.getCapVolJacobianFunction();
final DoubleMatrix1D pos = new DoubleMatrix1D(vol);
final DoubleMatrix1D capVols = func.evaluate(pos);
final DoubleMatrix2D jac = jacFunc.evaluate(pos);
final DoubleMatrix2D jacFD = jacFDFunc.evaluate(pos);
final int n = capVols.getNumberOfElements();
for (int i = 0; i < n; i++) {
assertEquals(vol, capVols.getEntry(i), 1e-9);
assertEquals(1.0, jac.getEntry(i, 0), 5e-8);
assertEquals(1.0, jacFD.getEntry(i, 0), 1e-6);
}
}
}