/** * 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.ArrayList; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.ArrayUtils; import com.google.common.primitives.Doubles; import com.opengamma.analytics.financial.instrument.index.IborIndex; import com.opengamma.analytics.financial.interestrate.payments.derivative.CapFloorIbor; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.financial.model.volatility.SimpleOptionData; import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurface; 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 decomposes a set of caps or floors to the unique set of underlying caplets (or floorlets) to allow * efficient simultaneous pricing of the caps. * {@link CapFloorPricer} */ public class MultiCapFloorPricer { private final int _nCaps; private final int _nCaplets; private final SimpleOptionData[] _capletsArray; private final double[] _capletExp; private final double[] _strikes; private final double[] _capStartTimes; private final double[] _capEndTimes; //maps //this maps from a cap to the position of an option in _capletsArray private final int[][] _capToCapletsMap; //this maps from a cap to a set of SimpleOptionData - the same thing can be achieved by using _capToCapletsMap on _capletsArray private final SimpleOptionData[][] _capToOptionsMap; /** * * @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 MultiCapFloorPricer(List<CapFloor> caps, MulticurveProviderInterface curves) { ArgumentChecker.noNulls(caps, "null caps"); ArgumentChecker.notNull(curves, "null curve"); IborIndex iborIndex = caps.get(0).getNthPayment(0).getIndex(); _nCaps = caps.size(); _capToCapletsMap = new int[_nCaps][]; _capToOptionsMap = new SimpleOptionData[_nCaps][]; // ensure a unique set of caplets in ascending order of strike then fixing time Set<CapFloorIbor> capletSet = new TreeSet<>(new CapletsComparator()); Set<Double> strikes = new TreeSet<>(); Set<Double> expiries = new TreeSet<>(); Set<Double> capStartTimes = new TreeSet<>(); Set<Double> capEndTimes = new TreeSet<>(); int count = 0; Iterator<CapFloor> iter = caps.iterator(); while (iter.hasNext()) { CapFloor cap = iter.next(); // check all the caps are on the same index ArgumentChecker.isTrue(iborIndex.equals(cap.getNthPayment(0).getIndex()), "caps of different index"); capStartTimes.add(cap.getStartTime()); capEndTimes.add(cap.getEndTime()); CapFloorIbor[] capletArray = cap.getPayments(); int n = capletArray.length; _capToCapletsMap[count++] = new int[n]; for (int j = 0; j < n; j++) { CapFloorIbor caplet = capletArray[j]; strikes.add(caplet.getStrike()); expiries.add(caplet.getFixingTime()); capletSet.add(caplet); } } _capStartTimes = ArrayUtils.toPrimitive(capStartTimes.toArray(new Double[0])); _capEndTimes = ArrayUtils.toPrimitive(capEndTimes.toArray(new Double[0])); _strikes = ArrayUtils.toPrimitive(strikes.toArray(new Double[0])); _capletExp = ArrayUtils.toPrimitive(expiries.toArray(new Double[0])); //represent the unique set of caplets as SimpleOptionData _capletsArray = CapFloorDecomposer.toOptions(capletSet.toArray(new CapFloorIbor[0]), curves); _nCaplets = _capletsArray.length; // Form a map from caplets in individual caps to the master caplet list (we are only sorting the extra references here) List<CapFloorIbor> capletList = new ArrayList<>(capletSet); for (int i = 0; i < _nCaps; i++) { CapFloor cap = caps.get(i); CapFloorIbor[] capletArray = cap.getPayments(); int n = capletArray.length; _capToOptionsMap[i] = new SimpleOptionData[n]; for (int j = 0; j < n; j++) { int index = capletList.indexOf(capletArray[j]); _capToCapletsMap[i][j] = index; _capToOptionsMap[i][j] = _capletsArray[index]; } } } /** * Order caplets by (ascending) order of fixing time, then by (ascending) order of strike. */ private class CapletsComparator implements Comparator<CapFloorIbor> { @Override public int compare(CapFloorIbor o1, CapFloorIbor o2) { int a = Doubles.compare(o1.getStrike(), o2.getStrike()); if (a != 0) { return a; } return Doubles.compare(o1.getFixingTime(), o2.getFixingTime()); } } /** * get the volatility of the underlying caplets (ordered by expiry then strike), picked off a (caplet) * volatility surface * @param volSurface A volatility surface * @return caplet volatilities */ public double[] getCapletVols(VolatilitySurface volSurface) { int nCaplets = _capletsArray.length; double[] vols = new double[nCaplets]; for (int i = 0; i < nCaplets; i++) { SimpleOptionData caplet = _capletsArray[i]; vols[i] = volSurface.getVolatility(caplet.getTimeToExpiry(), caplet.getStrike()); } return vols; } /** * Price a set of caps/floors (that will generally share some caplets/floorlets) using a (caplet) volatility surface. This will give a * (Black) volatility dependent on the strike and expiry of each caplet. The individual cap prices are of course the sum of the prices of each of * their constituent caplets. * @param volSurface the (Black) volatility surface of the underlying caplets * @return The cap/floor prices (in the same order the caps were given in the constructor) */ public double[] price(VolatilitySurface volSurface) { return priceFromCapletVols(getCapletVols(volSurface)); } /** * 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 capletVols The (Black) volatility of caplets. These <b>must</b> be order by (ascending) order of fixing time, * then by (ascending) order of strike. * @return The cap/floor prices (in the same order the caps were given in the constructor) */ public double[] priceFromCapletVols(double[] capletVols) { ArgumentChecker.notEmpty(capletVols, "null caplet volatilities"); ArgumentChecker.isTrue(_nCaplets == capletVols.length, "Expected {} caplet vols but given ", _nCaplets, capletVols.length); double[] capletPrices = new double[_nCaplets]; for (int i = 0; i < _nCaplets; i++) { capletPrices[i] = BlackFormulaRepository.price(_capletsArray[i], capletVols[i]); } return priceFromCapletPrices(capletPrices); } /** * Price a set of cap/floors from the implied volatilities of the caps/floors - this (by definition) is the common volatility applied to each of the underlying * caplets making up the cap. Since different caps will likely have some caplets in common, this pricing involves pricing the same caplets with different * volatilities depending on what cap you are considering. * @param capVolatilities the cap/floor (Black) volatilities. These must be in the same order as the cap passed to the constructor. * @return The cap/floor prices (in the same order the caps were given in the constructor) */ public double[] price(double[] capVolatilities) { ArgumentChecker.notEmpty(capVolatilities, "null cap volatilities"); ArgumentChecker.isTrue(_nCaps == capVolatilities.length, "capVolatilities wrong length"); double[] res = new double[_nCaps]; for (int i = 0; i < _nCaps; i++) { res[i] = BlackFormulaRepository.price(_capToOptionsMap[i], capVolatilities[i]); } return res; } /** * Price a set of caps/floors from the (Black) prices of the (unique set of) underlying caplets. * @param capletPrices These <b>must</b> be order by (ascending) order of fixing time, * then by (ascending) order of strike. * @return The cap/floor prices (in the same order the caps were given in the constructor) */ protected double[] priceFromCapletPrices(double[] capletPrices) { return aggregateToCaps(capletPrices); } /** * The implied volatilities for a set of caps from their prices. The implied volatility of a cap (or floor) is defined as the common (Black) * volatility applied to each of the constituent caplets such that the sum of the (Black) prices of the caplets equals the cap price. As the caps will generally * share some caplets, this is inconsistent as a model since a forward rate (which forms the payoff of a caplet) will 'see' a different volatility depending * on what cap is being priced. The cap implied volatilities should be viewed as nothing more than monotonic mapping from prices. * @param capPrices The cap prices (in the same order the caps were given in the constructor) * @return The cap/floor implied volatilities (in the same order the caps were given in the constructor) */ public double[] impliedVols(double[] capPrices) { ArgumentChecker.notEmpty(capPrices, "null cap prices"); ArgumentChecker.isTrue(_nCaps == capPrices.length, "capPrices wrong length"); double[] res = new double[_nCaps]; for (int i = 0; i < _nCaps; i++) { res[i] = BlackFormulaRepository.impliedVolatility(_capToOptionsMap[i], capPrices[i]); } return res; } /** * The implied volatilities for a set of caps from a model that describes the (Black) volatility of the individual constituent caplets. The individual cap * prices are of course the sum of the prices of each of their constituent caplets. The implied volatility of a cap (or floor) is defined as the common (Black) * volatility applied to each of the constituent caplets such that the sum of the (Black) prices of the caplets equals the cap price. * @param volSurface model describing the (Black) volatility of the underlying caplets * @return The cap/floor implied volatilities (in the same order the caps were given in the constructor) */ public double[] impliedVols(VolatilitySurface volSurface) { double[] prices = price(volSurface); return impliedVols(prices); } /** * The sensitivity of the prices of a set of caps to their implied volatility * @param capVolatilities the cap/floor (Black) volatilities * @return The cap/floor vega (in the same order the caps were given in the constructor) */ public double[] vega(double[] capVolatilities) { ArgumentChecker.notEmpty(capVolatilities, "null cap volatilities"); ArgumentChecker.isTrue(_nCaps == capVolatilities.length, "capVolatilities wrong length"); double[] res = new double[_nCaps]; for (int i = 0; i < _nCaps; i++) { int n = _capToCapletsMap[i].length; double sum = 0.0; for (int j = 0; j < n; j++) { sum += BlackFormulaRepository.vega(_capToOptionsMap[i][j], capVolatilities[i]); } res[i] = sum; } return res; } /** * 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 capletVols The volatilities of all the caplets that make up the set of caps * @return vega matrix */ public DoubleMatrix2D vegaFromCapletVols(double[] capletVols) { ArgumentChecker.notEmpty(capletVols, "null caplet volatilities"); ArgumentChecker.isTrue(_nCaplets == capletVols.length, "Expected {} caplet vols but given ", _nCaplets, capletVols.length); double[] capletVega = new double[_nCaplets]; for (int i = 0; i < _nCaplets; i++) { capletVega[i] = BlackFormulaRepository.vega(_capletsArray[i], capletVols[i]); } DoubleMatrix2D jac = new DoubleMatrix2D(_nCaps, _nCaplets); for (int i = 0; i < _nCaps; i++) { double[] data = jac.getData()[i]; int[] indices = _capToCapletsMap[i]; for (int index : indices) { data[index] = capletVega[index]; } } return jac; } /** * This vega matrix gives the sensitivity of the implied volatility of the ith cap to the volatility of the jth * caplet. of course if a cap does not contain a particular caplet, that entry will be zero. * @param capletVols The volatilities of all the caplets that make up the set of caps * @return cap volatility-vega matrix */ public DoubleMatrix2D capVolVega(double[] capletVols) { //cap vega matrix - sensitivity of cap prices to the volatilities of the caplets DoubleMatrix2D vega = vegaFromCapletVols(capletVols); double[] capPrices = priceFromCapletVols(capletVols); double[] capVols = impliedVols(capPrices); //sensitivity of the cap prices to their volatilities double[] capVega = vega(capVols); int nCaplets = capletVols.length; DoubleMatrix2D capVolVega = new DoubleMatrix2D(_nCaps, nCaplets); for (int i = 0; i < _nCaps; i++) { double[] temp = capVolVega.getData()[i]; double[] vegaRow = vega.getData()[i]; double invVega = 1.0 / capVega[i]; for (int j = 0; j < nCaplets; j++) { temp[j] = invVega * vegaRow[j]; } } return capVolVega; } /** * get the sorted array of unique caplet expiry times from the set of caps supplied * @return caplet expiry times */ public double[] getCapletExpiries() { return _capletExp; } /** * get the sorted array of unique strikes from the set of caps supplied * @return caplet expiry times */ public double[] getStrikes() { return _strikes; } /** * get the intrinsic (i.e. minimum) value of the caps - this is the cap price for zero volatility. * @return The intrinsic values */ public double[] getIntrinsicCapValues() { int n = _nCaplets; double[] intr = new double[n]; for (int i = 0; i < n; i++) { intr[i] = _capletsArray[i].getIntrinsicValue(); } return aggregateToCaps(intr); } /** * Aggregate additive values computer on caplets in caps * @param values computed on caplets * @return values aggregated to caps */ private double[] aggregateToCaps(double[] values) { double[] res = new double[_nCaps]; for (int i = 0; i < _nCaps; i++) { int[] indices = _capToCapletsMap[i]; int n = indices.length; double sum = 0; for (int j = 0; j < n; j++) { int index = indices[j]; sum += values[index]; } res[i] = sum; } return res; } /** * The ordered set of cap start times * @return cap start times */ public double[] getCapStartTimes() { return _capStartTimes; } /** * The ordered set of cap end times * @return cap end times */ public double[] getCapEndTimes() { return _capEndTimes; } /** * get an array of expiry-strike values (as a {@link DoublesPair} of the underlying caplets. These are order by * (ascending) order of fixing time, then by (ascending) order of strike. * @return DoublesPair of caplet expiry and strike */ public DoublesPair[] getExpiryStrikeArray() { DoublesPair[] res = new DoublesPair[_nCaplets]; for (int i = 0; i < _nCaplets; i++) { SimpleOptionData option = _capletsArray[i]; res[i] = DoublesPair.of(option.getTimeToExpiry(), option.getStrike()); } return res; } /** * Gets number of caps * @return the number of caps */ public int getNumCaps() { return _nCaps; } /** * Gets the number of unique caplets * @return the the number of unique caplets */ public int getNumCaplets() { return _nCaplets; } /** * for a particular cap (given by index), this gives the indices of its underlying caplets in the master caplet list * (which is ordered by fixing time, then strike) * @param index The index of the cap (using the same order as the constructor) * @return indices of the caplets belonging to the cap */ protected int[] getCapToCapletMap(int index) { return _capToCapletsMap[index]; } /** * get the caplet at a particular index (where the caplets are order by fixing time, then strike) as a {@link SimpleOptionData} * @param index the caplet index * @return caplet as a {@link SimpleOptionData} */ protected SimpleOptionData getOption(int index) { return _capletsArray[index]; } /** * get the underlying caplets (order by fixing time, then strike) as an array of {@link SimpleOptionData} * @return array of {@link SimpleOptionData} */ protected SimpleOptionData[] getCapletArray() { return _capletsArray; } /** * Get the forward rates for the period covered by the caplets. * @return the forward rates - these are order by (caplet) fixing time, then strike. */ public double[] getCapletForwardRates() { int n = _capletsArray.length; Set<Double> ts = new TreeSet<>(); for (int i = 0; i < n; i++) { ts.add(_capletsArray[i].getForward()); } return ArrayUtils.toPrimitive(ts.toArray(new Double[0])); } }