/** * 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.demo; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.testng.annotations.Test; import com.opengamma.analytics.financial.interestrate.capletstripping.CapFloor; import com.opengamma.analytics.financial.interestrate.capletstripping.CapletStripper; import com.opengamma.analytics.financial.interestrate.capletstripping.CapletStripperSABRModel; import com.opengamma.analytics.financial.interestrate.capletstripping.CapletStrippingResult; import com.opengamma.analytics.financial.interestrate.capletstripping.CapletStrippingSetup; import com.opengamma.analytics.financial.interestrate.capletstripping.MarketDataType; import com.opengamma.analytics.financial.interestrate.capletstripping.MultiCapFloorPricer; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve; import com.opengamma.analytics.math.function.DoublesVectorFunctionProvider; import com.opengamma.analytics.math.function.InterpolatedVectorFunctionProvider; import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolatorFactory; import com.opengamma.analytics.math.interpolation.Interpolator1D; 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.minimization.DoubleRangeLimitTransform; import com.opengamma.analytics.math.minimization.ParameterLimitsTransform; import com.opengamma.analytics.math.minimization.ParameterLimitsTransform.LimitType; import com.opengamma.analytics.math.minimization.SingleRangeLimitTransform; /** * Here the volatility of a particular caplet is given by the SABR model; the parameters of the model * (alpha, beta, rho & nu) are given by interpolated parameter term structures (beta is forced to be flat). * Since SABR can at most fit 4 volatilities (for a common expiry at different strikes), it is not possible to recover * all the cap values. The result is a very smooth caplet volatility surface (apart from the low expiry-strike corner) * that only recovers cap volatilities to around 100bps. */ public class SABRInterpolatedTermStructureDemo extends CapletStrippingSetup { private static int NUM_SABR_PARMS = 4; private static String[] PARAMETER_NAMES = new String[] {"ALPHA", "BETA", "RHO", "NU" }; private static Map<String, double[]> KNOTS; private static Map<String, ParameterLimitsTransform> TRANSFORMS; private static Map<String, Interpolator1D> INTERPOLATORS; private static DoublesVectorFunctionProvider[] VF_PROV; private static Interpolator1D DQ_INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.DOUBLE_QUADRATIC, Interpolator1DFactory.LINEAR_EXTRAPOLATOR); private static Interpolator1D FLAT_INTERPOLATOR = CombinedInterpolatorExtrapolatorFactory.getInterpolator(Interpolator1DFactory.LINEAR, Interpolator1DFactory.FLAT_EXTRAPOLATOR); private static final DoubleMatrix1D START; static { KNOTS = new LinkedHashMap<>(NUM_SABR_PARMS); KNOTS.put(PARAMETER_NAMES[0], new double[] {1, 2, 3, 5, 7, 10 }); KNOTS.put(PARAMETER_NAMES[1], new double[] {1 }); KNOTS.put(PARAMETER_NAMES[2], new double[] {1, 3, 7, }); KNOTS.put(PARAMETER_NAMES[3], new double[] {1, 2, 3, 5, 7, 10 }); TRANSFORMS = new LinkedHashMap<>(NUM_SABR_PARMS); TRANSFORMS.put(PARAMETER_NAMES[0], new SingleRangeLimitTransform(0.0, LimitType.GREATER_THAN)); TRANSFORMS.put(PARAMETER_NAMES[1], new DoubleRangeLimitTransform(0.1, 1)); TRANSFORMS.put(PARAMETER_NAMES[2], new DoubleRangeLimitTransform(-1, 1)); TRANSFORMS.put(PARAMETER_NAMES[3], new SingleRangeLimitTransform(0.0, LimitType.GREATER_THAN)); final double[] startVal = new double[] {0.2, 0.7, -0.2, 0.5 }; int nKnots = 0; for (int i = 0; i < NUM_SABR_PARMS; i++) { nKnots += KNOTS.get(PARAMETER_NAMES[i]).length; } START = new DoubleMatrix1D(nKnots); int pos = 0; for (int i = 0; i < NUM_SABR_PARMS; i++) { final int length = KNOTS.get(PARAMETER_NAMES[i]).length; Arrays.fill(START.getData(), pos, pos + length, TRANSFORMS.get(PARAMETER_NAMES[i]).transform(startVal[i])); pos += length; } INTERPOLATORS = new LinkedHashMap<>(NUM_SABR_PARMS); VF_PROV = new DoublesVectorFunctionProvider[NUM_SABR_PARMS]; for (int i = 0; i < NUM_SABR_PARMS; i++) { final String name = PARAMETER_NAMES[i]; final ParameterLimitsTransform trans = TRANSFORMS.get(name); if (name.equals("BETA")) { INTERPOLATORS.put(name, new TransformedInterpolator1D(FLAT_INTERPOLATOR, trans)); } else { INTERPOLATORS.put(name, new TransformedInterpolator1D(DQ_INTERPOLATOR, trans)); } VF_PROV[i] = new InterpolatedVectorFunctionProvider(INTERPOLATORS.get(name), KNOTS.get(name)); } } /** * This fits all caps (including ATM) by adjusting the knot values of the interpolation (SABR) parameter term structures * <p> * The output is this surface sampled on a grid (101 by 101), such that it can be plotted as an Excel surface plot (or imported into some other visualisation tool). */ @Test(description = "Demo of infering a caplet volatility surface") public void test() { final MulticurveProviderDiscount yc = getYieldCurves(); final List<CapFloor> allCaps = getAllCaps(); final double[] vols = getAllCapVols(); final MultiCapFloorPricer pricer = new MultiCapFloorPricer(allCaps, yc); final double[] t = pricer.getCapletExpiries(); final double[] errors = new double[allCaps.size()]; Arrays.fill(errors, 0.01); // 100bps final CapletStripper stripper = new CapletStripperSABRModel(pricer, VF_PROV); final CapletStrippingResult res = stripper.solve(vols, MarketDataType.VOL, errors, START); System.out.println(res); // Print out the market and model cap volatilities final double[] modelVols = res.getModelCapVols(); final int n = allCaps.size(); System.out.println("Market and model cap volatilities"); System.out.println("Strike\tExpiry\tMarket Vol\tModel Vol"); for (int i = 0; i < n; i++) { final CapFloor cap = allCaps.get(i); System.out.println(cap.getStrike() + "\t" + cap.getEndTime() + "\t" + vols[i] + "\t" + modelVols[i]); } // print the caplet volatilities System.out.println("\n"); res.printCapletVols(System.out); // print caplet volatility surface System.out.println("Caplet volatility Surface"); res.printSurface(System.out, 101, 101); System.out.println(); // make the (calibrated) SABR parameter term structures, in order to print smooth curves final DoubleMatrix1D fitParms = res.getFitParameters(); final InterpolatedDoublesCurve[] curves = new InterpolatedDoublesCurve[NUM_SABR_PARMS]; int pos = 0; for (int i = 0; i < NUM_SABR_PARMS; i++) { final String name = PARAMETER_NAMES[i]; final double[] knots = KNOTS.get(name); final int length = knots.length; final double[] y = new double[length]; System.arraycopy(fitParms.getData(), pos, y, 0, length); pos += length; curves[i] = new InterpolatedDoublesCurve(knots, y, INTERPOLATORS.get(name), true); } System.out.println("SABR parameter term structures"); System.out.println("time\talpha\tbeta\trho\tnu"); for (int i = 0; i < 101; i++) { final double time = t[t.length - 1] * i / (100.0); System.out.print(time); for (int j = 0; j < NUM_SABR_PARMS; j++) { System.out.print("\t" + curves[j].getYValue(time)); } System.out.println(); } } }