/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.provider.calculator.discounting;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import com.opengamma.analytics.financial.instrument.index.IborIndex;
import com.opengamma.analytics.financial.instrument.index.IndexON;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitor;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount;
import com.opengamma.analytics.financial.provider.description.interestrate.ParameterProviderInterface;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyMulticurveSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.multicurve.MultipleCurrencyParameterSensitivity;
import com.opengamma.analytics.financial.provider.sensitivity.parameter.ParameterSensitivityParameterCalculator;
import com.opengamma.analytics.math.curve.InterpolatedDoublesCurve;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.money.Currency;
/**
* Computes the cross-gamma to the curve parameters for multi-curve with all the curves in the same currency and instruments.
* The curves should be represented by a YieldCurve with an InterpolatedDoublesCurve on the zero-coupon rates.
* By default the gamma is computed using a one basis-point shift. This default can be change in a constructor.
* The results themselves are not scaled (the represent the second order derivative).
* <p> Reference: Interest Rate Cross-gamma for Single and Multiple Curves. OpenGamma Analysis 1, August 14
*/
public class CrossGammaMultiCurveCalculator {
/** Default size of bump: 1 basis point. */
private static final double BP1 = 1.0E-4;
/** The sensitivity calculator to the curve parameters used for the delta computation */
private final ParameterSensitivityParameterCalculator<ParameterProviderInterface> _psc;
/** The shift used for finite difference Gamma using two deltas. */
private final double _shift;
/**
* Constructor.
* @param shift The shift used for finite difference Gamma using two deltas. Shift be larger in absolute value than 1.0E-10.
* @param curveSensitivityCalculator The delta (curve sensitivity) calculator.
*/
public CrossGammaMultiCurveCalculator(final double shift,
final InstrumentDerivativeVisitor<ParameterProviderInterface, MultipleCurrencyMulticurveSensitivity> curveSensitivityCalculator) {
ArgumentChecker.notNull(curveSensitivityCalculator, "sensitivity calculator");
ArgumentChecker.isTrue(Math.abs(shift) > 1.0E-10, "shift should not be larger in absolute value than 1.0E-10");
_psc = new ParameterSensitivityParameterCalculator<>(curveSensitivityCalculator);
_shift = shift;
}
/**
* Constructor.
* The finite difference nump used is the default bump (1 basis point = 1.0E-4).
* @param curveSensitivityCalculator The delta (curve sensitivity) calculator.
*/
public CrossGammaMultiCurveCalculator(final InstrumentDerivativeVisitor<ParameterProviderInterface, MultipleCurrencyMulticurveSensitivity> curveSensitivityCalculator) {
ArgumentChecker.notNull(curveSensitivityCalculator, "sensitivity calculator");
_psc = new ParameterSensitivityParameterCalculator<>(curveSensitivityCalculator);
_shift = BP1;
}
/**
* Computes the gamma matrix for a given instrument.
* The curve provider should contain multi-curve in one currency which should each be of the
* type YieldCurve with an underlying InterpolatedDoublesCurve.
* @param instrument The instrument for which the cross-gamma should be computed. Same currency as the curves.
* @param multicurve The multi-curve provider.
* @return The cross-gamma matrices as map from curve name to matrix. One matrix is provided for each curve. It represents the intra-curve cross-gammas.
*/
public HashMap<String, DoubleMatrix2D> calculateCrossGammaIntraCurve(final InstrumentDerivative instrument, final MulticurveProviderDiscount multicurve) {
ArgumentChecker.notNull(instrument, "instrument");
ArgumentChecker.notNull(multicurve, "multi-curve provider");
Set<String> names = multicurve.getAllNames();
int nbCurve = names.size();
String[] namesArray = names.toArray(new String[nbCurve]);
Currency ccy = checkUniqueCurrency(multicurve);
InterpolatedDoublesCurve[] interpolatedCurves = interpolatedCurves(multicurve, nbCurve, namesArray);
// Curves description
double[][] y = new double[nbCurve][];
double[][] x = new double[nbCurve][];
int[] nbNode = new int[nbCurve];
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
y[loopcurve] = interpolatedCurves[loopcurve].getYDataAsPrimitive();
x[loopcurve] = interpolatedCurves[loopcurve].getXDataAsPrimitive();
nbNode[loopcurve] = x[loopcurve].length;
}
// Initial sensitivity
MultipleCurrencyParameterSensitivity ps0 = _psc.calculateSensitivity(instrument, multicurve);
double[][] ps0Array = sensitivitiesAsArrayOfMatrix(nbCurve, namesArray, ccy, nbNode, ps0);
// Bump and recompute for each curve and each point
HashMap<String, DoubleMatrix2D> result = new HashMap<>();
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
MultipleCurrencyParameterSensitivity[] psShift = new MultipleCurrencyParameterSensitivity[nbNode[loopcurve]];
double[][] gammaArray = new double[nbNode[loopcurve]][nbNode[loopcurve]];
for (int loopnode = 0; loopnode < nbNode[loopcurve]; loopnode++) {
final double[] yieldBumped = y[loopcurve].clone();
yieldBumped[loopnode] += _shift;
MulticurveProviderDiscount multicurveBumped = bumpedProvider(multicurve, namesArray[loopcurve],
interpolatedCurves[loopcurve].getInterpolator(), x[loopcurve], yieldBumped);
psShift[loopnode] = _psc.calculateSensitivity(instrument, multicurveBumped);
DoubleMatrix1D sensiCurve = psShift[loopnode].getSensitivity(namesArray[loopcurve], ccy);
double[] psShiftArray;
if (sensiCurve != null) {
psShiftArray = sensiCurve.getData();
} else {
psShiftArray = new double[nbNode[loopcurve]];
}
for (int loopnode2 = 0; loopnode2 < nbNode[loopcurve]; loopnode2++) {
gammaArray[loopnode][loopnode2] = (psShiftArray[loopnode2] - ps0Array[loopcurve][loopnode2]) / _shift;
}
}
DoubleMatrix2D gammaMat = new DoubleMatrix2D(gammaArray);
result.put(namesArray[loopcurve], gammaMat);
}
return result;
}
/**
* Computes the cross-gamma matrix for a given instrument.
* The curve provider should contain multi-curve in one currency which should each be of the
* type YieldCurve with an underlying InterpolatedDoublesCurve.
* @param instrument The instrument for which the cross-gamma should be computed. Same currency as the curves.
* @param multicurve The multi-curve provider.
* @return The cross-gamma matrix. It represents the cross-curve cross-gamma.
* The order of the curve is the one provided by the {@link MulticurveProviderDiscount#getAllNames()}.
*/
public DoubleMatrix2D calculateCrossGammaCrossCurve(final InstrumentDerivative instrument, final MulticurveProviderDiscount multicurve) {
ArgumentChecker.notNull(instrument, "instrument");
ArgumentChecker.notNull(multicurve, "multi-curve provider");
Set<String> names = multicurve.getAllNames();
int nbCurve = names.size();
String[] namesArray = names.toArray(new String[nbCurve]);
Currency ccy = checkUniqueCurrency(multicurve);
InterpolatedDoublesCurve[] interpolatedCurves = interpolatedCurves(multicurve, nbCurve, namesArray);
// Curves description
double[][] y = new double[nbCurve][];
double[][] x = new double[nbCurve][];
int[] nbNodeByCurve = new int[nbCurve];
int nbNodeTotal = 0;
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
y[loopcurve] = interpolatedCurves[loopcurve].getYDataAsPrimitive();
x[loopcurve] = interpolatedCurves[loopcurve].getXDataAsPrimitive();
nbNodeByCurve[loopcurve] = x[loopcurve].length;
nbNodeTotal += nbNodeByCurve[loopcurve];
}
// Initial sensitivity
MultipleCurrencyParameterSensitivity ps0 = _psc.calculateSensitivity(instrument, multicurve);
double[][] ps0Array = sensitivitiesAsArrayOfMatrix(nbCurve, namesArray, ccy, nbNodeByCurve, ps0);
// Bump and recompute for each curve and each point
double[][] gammaArray = new double[nbNodeTotal][nbNodeTotal];
int loopnodetotal = 0;
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
MultipleCurrencyParameterSensitivity[] psShift = new MultipleCurrencyParameterSensitivity[nbNodeByCurve[loopcurve]];
for (int loopnode = 0; loopnode < nbNodeByCurve[loopcurve]; loopnode++) {
final double[] yieldBumped = y[loopcurve].clone();
yieldBumped[loopnode] += _shift;
MulticurveProviderDiscount multicurveBumped = bumpedProvider(multicurve, namesArray[loopcurve],
interpolatedCurves[loopcurve].getInterpolator(), x[loopcurve], yieldBumped);
psShift[loopnode] = _psc.calculateSensitivity(instrument, multicurveBumped);
int loopnodetotal2 = 0;
for (int loopcurve2 = 0; loopcurve2 < nbCurve; loopcurve2++) {
DoubleMatrix1D sensiCurve = psShift[loopnode].getSensitivity(namesArray[loopcurve2], ccy);
double[] psShiftArray = new double[nbNodeByCurve[loopcurve2]];
if (sensiCurve != null) {
psShiftArray = sensiCurve.getData();
}
for (int loopnode2 = 0; loopnode2 < nbNodeByCurve[loopcurve2]; loopnode2++) {
gammaArray[loopnodetotal][loopnodetotal2] = (psShiftArray[loopnode2] - ps0Array[loopcurve2][loopnode2]) / _shift;
loopnodetotal2++;
}
}
loopnodetotal++;
}
}
return new DoubleMatrix2D(gammaArray);
}
/**
* Transform the sensitivities as a sensitivity into a array of array to be used in the computations.
* @param nbCurve The number of curves.
* @param names The curves names.
* @param ccy The currency of the curves.
* @param nbNodeByCurve The number of node for each curve.
* @param ps The sensitivity.
* @return The array. When no sensitivity is present an array of zeros is returned.
*/
private double[][] sensitivitiesAsArrayOfMatrix(int nbCurve, String[] names, Currency ccy, int[] nbNodeByCurve, MultipleCurrencyParameterSensitivity ps) {
DoubleMatrix1D[] ps0Mat = new DoubleMatrix1D[nbCurve];
double[][] ps0Array = new double[nbCurve][];
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
ps0Mat[loopcurve] = ps.getSensitivity(names[loopcurve], ccy);
if (ps0Mat[loopcurve] == null) {
ps0Array[loopcurve] = new double[nbNodeByCurve[loopcurve]];
} else {
ps0Array[loopcurve] = ps0Mat[loopcurve].getData();
}
}
return ps0Array;
}
/**
* Check that all the discounting and forward curves in the provider are related to the same currency.
* @param multicurve The multi-curve provider.
* @return The unique currency of the provider.
*/
private Currency checkUniqueCurrency(final MulticurveProviderDiscount multicurve) {
Set<Currency> ccys = multicurve.getCurrencies();
ArgumentChecker.isTrue(ccys.size() == 1, "only one currency allowed for multi-curve gamma");
Currency ccy = ccys.iterator().next();
Set<IborIndex> iborIndexes = multicurve.getIndexesIbor();
for (IborIndex index : iborIndexes) {
ArgumentChecker.isTrue(index.getCurrency().equals(ccy), "Ibor index should be in the same currency as discounting curve");
}
Set<IndexON> onIndexes = multicurve.getIndexesON();
for (IndexON index : onIndexes) {
ArgumentChecker.isTrue(index.getCurrency().equals(ccy), "Overnight index should be in the same currency as discounting curve");
}
return ccy;
}
/**
* Check that all the curves are of the type YieldCurve backed by an InterpolatedDoublesCurve.
* @param multicurve The multi-curve provider.
* @param nbCurve The number of curves.
* @param names The name of the curves.
* @return The array of interpolated curves.
*/
private InterpolatedDoublesCurve[] interpolatedCurves(final MulticurveProviderDiscount multicurve, int nbCurve, String[] names) {
InterpolatedDoublesCurve[] interpolatedCurves = new InterpolatedDoublesCurve[nbCurve];
for (int loopcurve = 0; loopcurve < nbCurve; loopcurve++) {
YieldAndDiscountCurve curve = multicurve.getCurve(names[loopcurve]);
ArgumentChecker.isTrue(curve instanceof YieldCurve, "curve should be YieldCurve");
YieldCurve yieldCurve = (YieldCurve) curve;
ArgumentChecker.isTrue(yieldCurve.getCurve() instanceof InterpolatedDoublesCurve, "Yield curve should be based on InterpolatedDoublesCurve");
interpolatedCurves[loopcurve] = (InterpolatedDoublesCurve) yieldCurve.getCurve();
}
return interpolatedCurves;
}
/**
* Bump the interpolated curve of a provider.
* @param multicurve The current multi-curve.
* @param name The name of the curve to bump.
* @param interpolator The interpolator associated to the curve to bump.
* @param x The nodes of the interpolated curve.
* @param yieldBumped The yield after the bump.
* @return The bump curve provider.
*/
private MulticurveProviderDiscount bumpedProvider(final MulticurveProviderDiscount multicurve, String name,
Interpolator1D interpolator, double[] x, final double[] yieldBumped) {
List<Currency> nameCcys = multicurve.getCurrencyForName(name);
List<IborIndex> nameIbors = multicurve.getIborIndexForName(name);
List<IndexON> nameOns = multicurve.getOvernightIndexForName(name);
final YieldAndDiscountCurve curveBumped = new YieldCurve(name,
new InterpolatedDoublesCurve(x, yieldBumped, interpolator, true));
MulticurveProviderDiscount multicurveBumped = new MulticurveProviderDiscount();
multicurveBumped.setForexMatrix(multicurve.getFxRates());
for (Currency loopccy : multicurve.getCurrencies()) {
if (nameCcys.contains(loopccy)) {
multicurveBumped.setCurve(loopccy, curveBumped);
} else {
multicurveBumped.setCurve(loopccy, multicurve.getCurve(loopccy));
}
}
for (IborIndex loopibor : multicurve.getIndexesIbor()) {
if (nameIbors.contains(loopibor)) {
multicurveBumped.setCurve(loopibor, curveBumped);
} else {
multicurveBumped.setCurve(loopibor, multicurve.getCurve(loopibor));
}
}
for (IndexON loopon : multicurve.getIndexesON()) {
if (nameOns.contains(loopon)) {
multicurveBumped.setCurve(loopon, curveBumped);
} else {
multicurveBumped.setCurve(loopon, multicurve.getCurve(loopon));
}
}
return multicurveBumped;
}
}