/** * 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 com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunctionProvider; import com.opengamma.analytics.financial.model.volatility.discrete.DiscreteVolatilityFunctionProviderFromInterpolatedTermStructure; import com.opengamma.analytics.math.FunctionUtils; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.DoubleQuadraticInterpolator1D; import com.opengamma.analytics.math.interpolation.Interpolator1D; import com.opengamma.analytics.math.interpolation.Interpolator1DFactory; import com.opengamma.analytics.math.interpolation.LinearExtrapolator1D; import com.opengamma.analytics.math.interpolation.TransformedInterpolator1D; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; 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.ArgumentChecker; /** * This represents the (caplet) volatility surface using an interpolated curve in the expiry direction only (i.e. * there is no strike dependence). This is mainly used to strip a single strike, in which case one can have as many knots * as caps (of a single strike) and root find for the knot values. * <P> * If used with multiple strikes, the lack of strike dependence will make it impossible to recover the market values. * @see {@link CapletStripperPSplineTermStructure} */ public class CapletStripperInterpolatedTermStructure implements CapletStripper { 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); private final MultiCapFloorPricer _pricer; private final Interpolator1D _interpolator; private final ParameterLimitsTransform _transform; private final double[] _knots; /** * Set up the stripper with a double-quadratic interpolator ({@link DoubleQuadraticInterpolator1D}) and a linear extrapolator ({@link LinearExtrapolator1D}). * The transformation (using {@link SingleRangeLimitTransform}) ensures the caplet volatility is always positive (regardless of the value of the knots). * Here the knots positions are auto generated. * @param pricer The pricer (which contained the details of the market values of the caps/floors) */ public CapletStripperInterpolatedTermStructure(final MultiCapFloorPricer pricer) { ArgumentChecker.notNull(pricer, "pricer"); _pricer = pricer; _knots = null; _transform = TRANSFORM; _interpolator = new TransformedInterpolator1D(CombinedInterpolatorExtrapolatorFactory.getInterpolator(DEFAULT_INTERPOLATOR, DEFAULT_EXTRAPOLATOR), _transform); } /** * Set up the stripper with a transformed double-quadratic interpolator ({@link DoubleQuadraticInterpolator1D}) and a linear extrapolator ({@link LinearExtrapolator1D}). * The transformation (using {@link SingleRangeLimitTransform}) ensures the caplet volatility is always positive (regardless of the value of the knots). * @param pricer The pricer (which contained the details of the market values of the caps/floors) * @param knots The knots. The knot positions will have a large effect on the shape of the term structure, and hence * the quality of the solution. The number of knots must not exceed the number of caps. */ public CapletStripperInterpolatedTermStructure(MultiCapFloorPricer pricer, double[] knots) { ArgumentChecker.notNull(pricer, "pricer"); ArgumentChecker.notEmpty(knots, "knots"); ArgumentChecker.isTrue(pricer.getNumCaps() >= knots.length, "#knots ({}) is greater than number of caps ({}). Please reduce the number of knots", knots.length, pricer.getNumCaps()); _pricer = pricer; _transform = TRANSFORM; _interpolator = new TransformedInterpolator1D(CombinedInterpolatorExtrapolatorFactory.getInterpolator(DEFAULT_INTERPOLATOR, DEFAULT_EXTRAPOLATOR), _transform); _knots = knots.clone(); } @Override public CapletStrippingResult solve(double[] marketValues, MarketDataType type) { CapletStrippingCore imp = getImp(marketValues); double[] impliedVol = type == MarketDataType.PRICE ? _pricer.impliedVols(marketValues) : marketValues; DoubleMatrix1D start = getStartValue(impliedVol, imp.getNumModelParms()); if (type == MarketDataType.PRICE) { return imp.solveForCapPrices(marketValues, start); } else if (type == MarketDataType.VOL) { return imp.solveForCapVols(marketValues, start); } throw new IllegalArgumentException("Unknown MarketDataType " + type.toString()); } @Override public CapletStrippingResult solve(double[] marketValues, MarketDataType type, double[] errors) { CapletStrippingCore imp = getImp(marketValues); double[] impliedVol = type == MarketDataType.PRICE ? _pricer.impliedVols(marketValues) : marketValues; DoubleMatrix1D start = getStartValue(impliedVol, imp.getNumModelParms()); if (type == MarketDataType.PRICE) { return imp.solveForCapPrices(marketValues, errors, start); } else if (type == MarketDataType.VOL) { return imp.solveForCapVols(marketValues, errors, start); } throw new IllegalArgumentException("Unknown MarketDataType " + type.toString()); } @Override public CapletStrippingResult solve(double[] marketValues, MarketDataType type, DoubleMatrix1D guess) { CapletStrippingCore imp = getImp(marketValues); if (type == MarketDataType.PRICE) { return imp.solveForCapPrices(marketValues, guess); } else if (type == MarketDataType.VOL) { return imp.solveForCapVols(marketValues, guess); } throw new IllegalArgumentException("Unknown MarketDataType " + type.toString()); } @Override public CapletStrippingResult solve(double[] marketValues, MarketDataType type, double[] errors, DoubleMatrix1D guess) { CapletStrippingCore imp = getImp(marketValues); if (type == MarketDataType.PRICE) { return imp.solveForCapPrices(marketValues, errors, guess); } else if (type == MarketDataType.VOL) { return imp.solveForCapVols(marketValues, errors, guess); } throw new IllegalArgumentException("Unknown MarketDataType " + type.toString()); } private CapletStrippingCore getImp(double[] values) { ArgumentChecker.notEmpty(values, "values"); int nCaps = _pricer.getNumCaps(); ArgumentChecker.isTrue(nCaps == values.length, "Expected {} cap prices, but only given {}", nCaps, values.length); double[] knots = _knots == null ? getKnots(_pricer.getCapStartTimes(), _pricer.getCapEndTimes(), nCaps) : _knots; DiscreteVolatilityFunctionProvider volPro = new DiscreteVolatilityFunctionProviderFromInterpolatedTermStructure(knots, _interpolator); return new CapletStrippingCore(_pricer, volPro); } /** * get a rough starting value of model parameters from cap volatilities. * @param capVols cap volatilities * @param n number of model parameters * @return starting guess */ public DoubleMatrix1D getStartValue(double[] capVols, int n) { double[] temp = new double[n]; System.arraycopy(capVols, 0, temp, 0, n); if (n > capVols.length) { Arrays.fill(temp, capVols.length, n, capVols[capVols.length - 1]); } for (int i = 0; i < n; i++) { temp[i] = _transform.transform(temp[i]); } return new DoubleMatrix1D(temp); } private double[] getKnots(double[] s, double[] e, int nCaps) { int ns = s.length; int ne = e.length; double[] temp = new double[ns + ne]; System.arraycopy(s, 0, temp, 0, ns); System.arraycopy(e, 0, temp, ns, ne); double[] times = FunctionUtils.unique(temp); ArgumentChecker.isTrue(times.length >= nCaps, "Cannot auto generate knots for this set of caps. Please supply knots"); if (times.length == nCaps) { return times; } // use the first nCaps times double[] knots = new double[nCaps]; System.arraycopy(times, 0, knots, 0, nCaps); return knots; } }