/**
* 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 java.util.Arrays;
import java.util.List;
import org.testng.annotations.Test;
import cern.jet.random.engine.MersenneTwister;
import cern.jet.random.engine.MersenneTwister64;
import cern.jet.random.engine.RandomEngine;
import com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunctionProvider;
import com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunctionProviderFromInterpolatedTermStructure;
import com.opengamma.analytics.math.differentiation.VectorFieldFirstOrderDifferentiator;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.interpolation.TransformedInterpolator1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.analytics.math.minimization.ParameterLimitsTransform;
import com.opengamma.analytics.math.minimization.ParameterLimitsTransform.LimitType;
import com.opengamma.analytics.math.minimization.SingleRangeLimitTransform;
import com.opengamma.util.test.TestGroup;
/**
*
*/
@Test(groups = TestGroup.UNIT)
public class CapletStripperInterpolatedTermStructureTest extends CapletStrippingSetup {
private static final RandomEngine RANDOM = new MersenneTwister64(MersenneTwister.DEFAULT_SEED);
private static final VectorFieldFirstOrderDifferentiator DIFF = new VectorFieldFirstOrderDifferentiator();
private static final String DEFAULT_INTERPOLATOR = Interpolator1DFactory.DOUBLE_QUADRATIC;
private static final String DEFAULT_EXTRAPOLATOR = Interpolator1DFactory.LINEAR_EXTRAPOLATOR;
private static final ParameterLimitsTransform TRANSFORM = new SingleRangeLimitTransform(0.0, LimitType.GREATER_THAN);
@Test(expectedExceptions = IllegalArgumentException.class)
public void badKnotsTest() {
List<CapFloor> caps = getATMCaps();
MultiCapFloorPricer pricer = new MultiCapFloorPricer(caps, getYieldCurves());
int nCaps = caps.size();
double[] knots = new double[nCaps + 1];
for (int i = 0; i <= nCaps; i++) {
knots[i] = i;
}
new CapletStripperInterpolatedTermStructure(pricer, knots);
}
/**
* Test fitting the ATM Caps. This implicitly makes the assumption that the caplet volatilities have no strike
* dependence, so caplets belonging the different caps but with the same expiry, will have the same volatility despite
* having different strikes.
*/
@Test
public void atmTest() {
MultiCapFloorPricer pricer = new MultiCapFloorPricer(getATMCaps(), getYieldCurves());
CapletStripper stripper = new CapletStripperInterpolatedTermStructure(pricer);
DoubleMatrix1D expFitParms = new DoubleMatrix1D(0.0750077681018984, -0.0417132310902179, 0.443334588249104, 0.166354129225537, -0.0955443232821012, -0.535813313851482, -0.81400903918072);
double expChiSqr = 0.0;
testStripping(stripper, getATMCapPrices(), MarketDataType.PRICE, expChiSqr, expFitParms, 1e-8, false);
}
/**
* Fit caps at each absolute strike in turn
*/
@Test
public void singleStrikeTest() {
double[][] expFitParms = new double[][] { {0.07519558925857502, 0.06099595006987337, 0.37887791113990626, 0.2900814932812122, 0.06960865030557412, -0.05342521479925498 },
{0.2888421248987955, -0.0023192971048977065, -0.15913446980405835, -0.2962382393117878, -0.4773062729997372 },
{-0.17164504785623153, 0.17570630956403765, -0.09852806432927508, -0.23506962123817332, -0.41048624505277914, -0.6301356867782051 },
{0.3908797227276975, -0.3238072319276149, -0.5393466581064617, -0.7213032534286311 },
{-0.7283917245109782, 0.2553434284914208, -0.2378442519094908, -0.3704381964960703, -0.536862242806576, -0.8058029253911454 },
{0.827445095514852, -0.4198900590695616, -0.6269795252250308, -0.8875937407303798 },
{0.9532565315179226, 0.2851870481750914, 0.23441102409602055, -0.16207126482856957, -0.652262655104053, -0.9099696638475132 },
{0.9371181616741376, 0.34005473961956495, 0.23237903957042214, -0.19618930329921386, -0.40683572799405837, -0.6596470520871216, -0.9613088609863392 },
{0.7059889604800385, 0.45927089229020324, 0.11425056866252968, -0.36451987986750245 },
{0.955999407622807, 0.40220172717867936, 0.19913928224094274, -0.15816695045653018, -0.4590787700861603, -0.6932984278278611, -0.9875408880086803 },
{0.9448245261357552, 0.41772275182543434, 0.19395773066035696, -0.15365694720478604, -0.4229538051369399, -0.6989629404321959, -0.997435029791332 },
{0.9644014702150517, 0.4533491035091301, 0.1699873426536261, -0.16922659200325504, -0.42043388566407425, -0.6892676775943389, -1.0090832784881916 },
{0.6984521044968122, 0.5556741207844491, -0.1921052130631996, -0.39739908140320057, -0.684338560893483, -1.016715992733998 },
{1.1191378168668937, -0.18251768035905208, -0.3962744821361997, -0.6884914142373438, -0.9972361085539848 },
{0.5838225819129124, 0.17592675122488316, -0.16435508774233396, -0.4081223090812207, -0.6539226486174113, -0.9615281610297096 },
{0.5857751937847466, 0.18152902949991212, -0.16919316844578738, -0.39593555754486615, -0.6440943082426583, -0.9374060706820373 },
{1.149929104142057, -0.17606620627774683, -0.3775255884563137, -0.6097720432994848, -0.9129668308247708 },
{1.1522779659159654, -0.17449603280382392, -0.3744748246651557, -0.6040440327679161, -0.9031355380153249 } };
int n = getNumberOfStrikes();
CapletStripper[] strippers = new CapletStripper[n];
// first solve for prices
for (int i = 0; i < n; i++) {
MultiCapFloorPricer pricer = new MultiCapFloorPricer(getCaps(i), getYieldCurves());
strippers[i] = new CapletStripperInterpolatedTermStructure(pricer);
testStripping(strippers[i], getCapPrices(i), MarketDataType.PRICE, 0.0, new DoubleMatrix1D(expFitParms[i]), 1e-4, false);
}
// then solve for vols - there are two nested root finds here; the multi-dimensional one to find the knots values
// and the 1D one to find the implied volatility, hence the resultant fit parameters from a root find for cap
// volatility will differ slightly from the root find for prices - this is why the tolerance is 1e-4
for (int i = 0; i < n; i++) {
testStripping(strippers[i], getCapVols(i), MarketDataType.VOL, 0.0, new DoubleMatrix1D(expFitParms[i]), 1e-4, false);
}
}
/**
* Start from random initial guesses. If there is a unique solution, this should always be found, however due to the
* finite stopping criteria, the fit parameters (i.e. the stopping point) will only match to some tolerance
*/
@Test
public void randomStartTest() {
double[] vols = getCapVols(0);
double[] prices = getCapPrices(0);
MultiCapFloorPricer pricer = new MultiCapFloorPricer(getCaps(0), getYieldCurves()); // the 0.5% strikes
CapletStripper stripper = new CapletStripperInterpolatedTermStructure(pricer);
// these differ as discussed in singleStrikeTest
DoubleMatrix1D expVolFitParms = new DoubleMatrix1D(0.0751955883987435, 0.0609959501482534, 0.378877911140902, 0.290081493287973, 0.0696086502994876, -0.0534252147847549);
DoubleMatrix1D expPriceFitParms = new DoubleMatrix1D(0.075195589258575, 0.0609959500698733, 0.378877911139906, 0.290081493281212, 0.0696086503055741, -0.0534252147992549);
int nKnots = vols.length;
int nSamples = 20;
for (int i = 0; i < nSamples; i++) {
DoubleMatrix1D guess = new DoubleMatrix1D(nKnots);
for (int j = 0; j < nKnots; j++) {
guess.getData()[j] = 0.2 + 0.5 * RANDOM.nextDouble();
}
testStripping(stripper, vols, MarketDataType.VOL, guess, 0.0, expVolFitParms, 1e-7, false);
testStripping(stripper, prices, MarketDataType.PRICE, guess, 0.0, expPriceFitParms, 1e-7, false);
}
}
/**
* Try to fit all cap with a volatility surface that has no strike dependence.
* Clearly the global fit in this case will not be good.
*/
@Test
public void globalFitTest() {
double[] knots = new double[] {0.25, 0.5, 1.0, 2.0, 3.0, 5.0, 7.0, 10.0 };
MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
CapletStripper stripper = new CapletStripperInterpolatedTermStructure(pricer, knots);
double[] vols = getAllCapVols();
double[] prices = pricer.price(vols);
double[] vega = pricer.vega(vols);
int n = vols.length;
double[] errors = new double[n];
Arrays.fill(errors, 1e-3);// 10bps
for (int i = 0; i < n; i++) {
vega[i] *= errors[i];
}
// fit to prices (weighted by vega)
double expPriceChiSqr = 238653.409503741;
DoubleMatrix1D expPriceFitParms = new DoubleMatrix1D(-4.15580547109701, -0.304212974580643, 0.172831987560697, 0.198455163737413, -0.135298615671123, -0.565282388889337, -0.9691261199302,
-0.947227800414312);
testStripping(stripper, prices, MarketDataType.PRICE, vega, expPriceChiSqr, expPriceFitParms, 1e-7, false);
// fit to vols
double expVolChiSqr = 242619.18324317678;
DoubleMatrix1D expVolFitParms = new DoubleMatrix1D(-4.30213860658499, -0.317785074052976, 0.171692755804742, 0.202209112938613, -0.134975254807032, -0.554874332703476, -0.973564599389498,
-0.917346420590909);
testStripping(stripper, vols, MarketDataType.VOL, errors, expVolChiSqr, expVolFitParms, 1e-7, false);
// guess a start val
DoubleMatrix1D guess = new DoubleMatrix1D(knots.length, 0.3);
testStripping(stripper, prices, MarketDataType.PRICE, vega, guess, expPriceChiSqr, expPriceFitParms, 5e-5, false);
testStripping(stripper, vols, MarketDataType.VOL, errors, guess, expVolChiSqr, expVolFitParms, 1e-4, false);
}
/**
* check the Jacobian against the FD version
*/
@Test
public void jacobianTest() {
double[] knots = new double[] {0.25, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
MultiCapFloorPricer pricer = new MultiCapFloorPricer(getAllCaps(), getYieldCurves());
TransformedInterpolator1D interpolator = new TransformedInterpolator1D(CombinedInterpolatorExtrapolatorFactory.getInterpolator(DEFAULT_INTERPOLATOR, DEFAULT_EXTRAPOLATOR), TRANSFORM);
DiscreteVolatilityFunctionProvider pro = new DiscreteVolatilityFunctionProviderFromInterpolatedTermStructure(knots, interpolator);
CapletStrippingCore imp = new CapletStrippingCore(pricer, pro);
Function1D<DoubleMatrix1D, DoubleMatrix1D> func = imp.getCapVolFunction();
Function1D<DoubleMatrix1D, DoubleMatrix2D> jacFunc = imp.getCapVolJacobianFunction();
Function1D<DoubleMatrix1D, DoubleMatrix2D> jacFuncFD = DIFF.differentiate(func);
int n = knots.length;
DoubleMatrix1D pos = new DoubleMatrix1D(n);
int nSamples = 20;
for (int run = 0; run < nSamples; run++) {
for (int i = 0; i < n; i++) {
pos.getData()[i] = 6 * RANDOM.nextDouble() - 3.0;
}
compareJacobianFunc(jacFunc, jacFuncFD, pos, 1e-4);
}
}
}