/** * Copyright (C) 2013 - 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 com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.financial.model.volatility.SimpleOptionData; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderInterface; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.DoublesPair; /** * This aligns the underlying caplets onto a strike-expiry grid, and fills in gaps in the resultant grid with phantom * caplets (caplets that do not belong to any of the caps, and thus cannot have any effect on the cap prices). This * is used exclusively by {@link CapletStripperDirect}, which requires a grid of caplets. */ public class MultiCapFloorPricerGrid extends MultiCapFloorPricer { private final int _gridSize; // maps // this maps from the position of an option in _capletsArray to a position in the (flattened) grid. private final int[] _optionsToGridMap; // this maps from a cap to a set of points on the grid. private final int[][] _capToGridMap; /** * This aligns the underlying caplets onto a strike-expiry grid, and fills in gaps in the resultant grid with phantom * caplets (caplets that do not belong to any of the caps, and thus cannot have any effect on the cap prices). This * is used exclusively by {@link CapletStripperDirect}, which requires a grid of caplets. * @param caps List of cap or floors (as {@link CapFloor}). The order is not important and will be retained by methods * returning cap values. * @param curves The discount and index curves */ public MultiCapFloorPricerGrid(List<CapFloor> caps, MulticurveProviderInterface curves) { super(caps, curves); _capToGridMap = new int[getNumCaps()][]; double[] strikes = getStrikes(); double[] capletExp = getCapletExpiries(); int nStrikes = strikes.length; int nExp = capletExp.length; _gridSize = nStrikes * nExp; _optionsToGridMap = new int[getNumCaplets()]; if (getNumCaplets() == _gridSize) { // here the options (caplets/floorlets) form a regular 2D grid in strike-expiry for (int i = 0; i < _gridSize; i++) { _optionsToGridMap[i] = i; } } else if (getNumCaplets() < _gridSize) { int count = 0; for (SimpleOptionData option : getCapletArray()) { double k = option.getStrike(); double t = option.getTimeToExpiry(); int kIndex = Arrays.binarySearch(getStrikes(), k); int tIndex = Arrays.binarySearch(getCapletExpiries(), t); _optionsToGridMap[count++] = kIndex * nExp + tIndex; } } else { throw new IllegalArgumentException("Something is wrong with the logic of MultiCapFloorPricerGrid"); } // form a map from cap to grid positions for (int i = 0; i < getNumCaps(); i++) { int[] map = getCapToCapletMap(i); int n = map.length; _capToGridMap[i] = new int[n]; for (int j = 0; j < n; j++) { _capToGridMap[i][j] = _optionsToGridMap[map[j]]; } } } /** * Price a set of caps/floors from the (Black) volatility of caplets on a strike-expiry grid. * This is mainly used to calibrate to cap prices by directly setting the individual caplet vols * @param capletVolGridValues The (Black) volatility of caplets on a (flatten) strike-expiry grid - this is flattened * row-wise (so you have a block at one strike, then a block at the next strike) * @return The cap/floor prices (in the same order the caps were given in the constructor) */ @Override public double[] priceFromCapletVols(double[] capletVolGridValues) { ArgumentChecker.notEmpty(capletVolGridValues, "null caplet volatilities"); ArgumentChecker.isTrue(_gridSize == capletVolGridValues.length, "Expect caplet vols on a (flatened) grid of {} strikes by {} expiries, so {} values. Given {} capletVols", getStrikes().length, getCapletExpiries().length, _gridSize, capletVolGridValues.length); // If _gridSize < _nCaplets not all the elements of capletVolGridValues are used. This is because some of the volatilities // refer to caplets not found in any caps (we are considering) and are in effect dummy values for calibration methods that // require a complete (i.e. no missing elements) grid. int n = getNumCaplets(); double[] capletPrices = new double[n]; for (int i = 0; i < n; i++) { capletPrices[i] = BlackFormulaRepository.price(getOption(i), capletVolGridValues[_optionsToGridMap[i]]); } return priceFromCapletPrices(capletPrices); } /** * This vega matrix gives the sensitivity of the ith cap to the volatility of the jth caplet (where the caplets are order by their expiry). of course * if a cap does not contain a particular caplet, that entry will be zero. * @param capletVolGridValues The volatilities of all the caplets that make up the set of caps * @return vega matrix */ @Override public DoubleMatrix2D vegaFromCapletVols(double[] capletVolGridValues) { ArgumentChecker.notEmpty(capletVolGridValues, "null caplet volatilities"); ArgumentChecker.isTrue(_gridSize == capletVolGridValues.length, "Expect caplet vols on a (flatened) grid of {} strikes by {} expiries, so {} values. Given {} capletVols", getStrikes().length, getCapletExpiries().length, _gridSize, capletVolGridValues.length); // do not compute the vega of the 'dummy' caplets double[] capletVega = new double[_gridSize]; int n = getNumCaplets(); for (int i = 0; i < n; i++) { int gridIndex = _optionsToGridMap[i]; capletVega[gridIndex] = BlackFormulaRepository.vega(getOption(i), capletVolGridValues[gridIndex]); } int nCaps = getNumCaps(); DoubleMatrix2D jac = new DoubleMatrix2D(nCaps, _gridSize); for (int i = 0; i < nCaps; i++) { double[] data = jac.getData()[i]; int[] indices = _capToGridMap[i]; for (int index : indices) { data[index] = capletVega[index]; } } return jac; } /** * get the size of the grid. Since phantom caplets may be used to complete the grid, this may be greater than the * actual number of (unique) caplets (returned by getNumCaplets()) * @return The size of the grid */ public int getGridSize() { return _gridSize; } @Override public DoublesPair[] getExpiryStrikeArray() { DoublesPair[] res = new DoublesPair[_gridSize]; double[] strikes = getStrikes(); double[] exp = getCapletExpiries(); int n = strikes.length; int m = exp.length; for (int i = 0; i < n; i++) { double k = strikes[i]; for (int j = 0; j < m; j++) { double t = exp[j]; res[j + i * m] = DoublesPair.of(t, k); } } return res; } }