/**
* 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.Iterator;
import java.util.List;
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.provider.description.interestrate.MulticurveProviderInterface;
import com.opengamma.util.ArgumentChecker;
/**
* Simplest possible caplet stripping algorithm. Each cap must be at the same strike (cap are normally quoted at fixed absolute strikes, except for the ATM
* which this cannot handle), and have the same start (this could be relaxed with better decomposition logic). The co-starting caps are decomposed into a set
* of spanning caps (simply by taking price difference). Since these spanning caps do not (by construction) share any underlying caplets, implied volatilities
* (i.e. the common volatility of the caplet set) can be found that price each spanning cap. The resultant expiry dependent caplet volatility curve with of course
* by piecewise constant.
*/
public class CapletStrippingBootstrap {
private final SimpleOptionData[][] _caplets;
private final double[] _intrinsicValues;
private final double[] _endTimes;
// private final List<CapFloorPricer> _capPricers;
/**
* Simple caplet bootstrapping
* @param caps All caps must have same start time and strike
* @param curves yield curves (i.e. discount and Ibor-projection)
*/
public CapletStrippingBootstrap(final List<CapFloor> caps, final MulticurveProviderInterface curves) {
ArgumentChecker.noNulls(caps, "caps null");
ArgumentChecker.notNull(curves, "null curves");
final int n = caps.size();
_caplets = new SimpleOptionData[n][];
_intrinsicValues = new double[n];
_endTimes = new double[n];
final Iterator<CapFloor> iter = caps.iterator();
final CapFloor firstCap = iter.next();
_caplets[0] = CapFloorDecomposer.toOptions(firstCap, curves);
_intrinsicValues[0] = intrinsicValue(_caplets[0]);
final double strike = firstCap.getStrike();
final double startTime = firstCap.getStartTime();
double endTime = firstCap.getEndTime();
_endTimes[0] = endTime;
int i1 = firstCap.getNumberOfPayments();
int ii = 1;
while (iter.hasNext()) {
final CapFloor cap = iter.next();
ArgumentChecker.isTrue(cap.getStrike() == strike, "caps must have same strike for this method");
ArgumentChecker.isTrue(cap.getStartTime() == startTime, "caps must be co-starting");
final double temp = cap.getEndTime();
ArgumentChecker.isTrue(temp > endTime, "caps must be in order of increasing end time"); // TODO remove this by sorting caps
// decompose caps
final int i2 = cap.getNumberOfPayments();
final CapFloorIbor[] caplets = cap.getPayments();
final CapFloorIbor[] uniqueCaplets = new CapFloorIbor[i2 - i1];
System.arraycopy(caplets, i1, uniqueCaplets, 0, i2 - i1);
_caplets[ii] = CapFloorDecomposer.toOptions(uniqueCaplets, curves);
_intrinsicValues[ii] = intrinsicValue(_caplets[ii]);
i1 = i2;
endTime = temp;
_endTimes[ii] = endTime;
ii++;
}
}
// intrinsic value
private double intrinsicValue(final SimpleOptionData[] data) {
final int n = data.length;
double sum = 0.0;
for (int i = 0; i < n; i++) {
sum += data[i].getIntrinsicValue();
}
return sum;
}
/**
* The caplet vols for each piecewise constant period
* @param mktCapFlPrices market prices of caps
* @return The set caplet/floorlet volatilities (indexed in ascending time order)
*/
public double[] capletVolsFromPrices(final double[] mktCapFlPrices) {
ArgumentChecker.notEmpty(mktCapFlPrices, "null cap prices");
final int n = _caplets.length;
ArgumentChecker.isTrue(n == mktCapFlPrices.length, "length of prices does not match number of caps");
ArgumentChecker.isTrue(mktCapFlPrices[0] > _intrinsicValues[0], "prices must be greater than or equal to their intrinsic values");
final double[] diffs = new double[n];
diffs[0] = mktCapFlPrices[0];
for (int i = 1; i < n; i++) {
diffs[i] = mktCapFlPrices[i] - mktCapFlPrices[i - 1];
ArgumentChecker.isTrue(diffs[i] >= _intrinsicValues[i], "prices must be greater than or equal to their intrinsic values");
}
return capletVolsFromPriceDiff(diffs);
}
private double[] capletVolsFromPriceDiff(final double[] diffs) {
final int n = diffs.length;
final double[] capletVols = new double[n];
for (int i = 0; i < n; i++) {
capletVols[i] = BlackFormulaRepository.impliedVolatility(_caplets[i], diffs[i]);
}
return capletVols;
}
/**
* The caplet vols for each piecewise constant period
* @param mktCapFlVols market implied volatilities of caps
* @return he set caplet/floorlet volatilities (indexed in ascending time order)
*/
public double[] capletVolsFromCapVols(final double[] mktCapFlVols) {
ArgumentChecker.notEmpty(mktCapFlVols, "null cap vols");
final int n = _caplets.length;
ArgumentChecker.isTrue(n == mktCapFlVols.length, "length of vols does not match number of caps");
final double[] mktCapFlPrices = new double[n];
mktCapFlPrices[0] = BlackFormulaRepository.price(_caplets[0], mktCapFlVols[0]);
for (int i = 1; i < n; i++) {
double sum = 0;
for (int j = 0; j <= i; j++) {
sum += BlackFormulaRepository.price(_caplets[j], mktCapFlVols[i]);
}
mktCapFlPrices[i] = sum;
}
return capletVolsFromPrices(mktCapFlPrices);
}
public double[] getEndTimes() {
return _endTimes;
}
}