/** * 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 com.google.common.collect.Iterators; 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.DiscountCurve; 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.differentiation.FiniteDifferenceType; import com.opengamma.analytics.math.differentiation.VectorFieldFirstOrderDifferentiator; import com.opengamma.analytics.math.function.Function1D; 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 a single curve. * The curve 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 curve. OpenGamma Quantitative Analysis 1, August 14 */ public class CrossGammaSingleCurveCalculator { /** Default size of bump: 1 basis point. */ private static final double BP1 = 1.0E-4; /** The tool used to differentiate the delta. */ private final VectorFieldFirstOrderDifferentiator _differentiator; /** 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. * The default shift is used (1.0E-4 = 1 basis point). The default finite difference is used (FiniteDifferenceType = FORWARD) * @param curveSensitivityCalculator The delta (curve sensitivity) calculator. */ public CrossGammaSingleCurveCalculator( final InstrumentDerivativeVisitor<ParameterProviderInterface, MultipleCurrencyMulticurveSensitivity> curveSensitivityCalculator) { _psc = new ParameterSensitivityParameterCalculator<>(curveSensitivityCalculator); _shift = BP1; _differentiator = new VectorFieldFirstOrderDifferentiator(FiniteDifferenceType.FORWARD, _shift); } /** * Constructor. * @param shift The shift used for finite difference Gamma using two deltas. * @param curveSensitivityCalculator The delta (curve sensitivity) calculator. */ public CrossGammaSingleCurveCalculator(final double shift, final InstrumentDerivativeVisitor<ParameterProviderInterface, MultipleCurrencyMulticurveSensitivity> curveSensitivityCalculator) { _psc = new ParameterSensitivityParameterCalculator<>(curveSensitivityCalculator); _shift = shift; _differentiator = new VectorFieldFirstOrderDifferentiator(FiniteDifferenceType.FORWARD, _shift); } /** * Computes the gamma matrix for a given instrument. The curve provider should contain only one curve which should be of the * type YieldCurve with an underlying InterpolatedDoublesCurve. * @param instrument The instrument for which the cross-gamma should be computed. * @param multicurve The multi-curve provider. * @return The cross-gamma matrix. */ public DoubleMatrix2D calculateCrossGamma(final InstrumentDerivative instrument, final MulticurveProviderDiscount multicurve) { ArgumentChecker.isTrue(multicurve.getAllNames().size() == 1, "provider should have only one curve for GammaSingleCurve computation"); String name = multicurve.getAllNames().iterator().next(); Currency ccy = Iterators.getOnlyElement(multicurve.getCurrencyForName(name).iterator()); YieldAndDiscountCurve curve = multicurve.getCurve(name); ArgumentChecker.isTrue(curve instanceof YieldCurve || curve instanceof DiscountCurve, "curve should be YieldCurve or DiscountCurve"); boolean isZc = curve instanceof YieldCurve; InterpolatedDoublesCurve interpolatedCurve; if (isZc) { YieldCurve yieldCurve = (YieldCurve) curve; ArgumentChecker.isTrue(yieldCurve.getCurve() instanceof InterpolatedDoublesCurve, "Yield curve should be based on InterpolatedDoublesCurve"); interpolatedCurve = (InterpolatedDoublesCurve) yieldCurve.getCurve(); } else { DiscountCurve discountCurve = (DiscountCurve) curve; ArgumentChecker.isTrue(discountCurve.getCurve() instanceof InterpolatedDoublesCurve, "Discount curve should be based on InterpolatedDoublesCurve"); interpolatedCurve = (InterpolatedDoublesCurve) discountCurve.getCurve(); } double[] y = interpolatedCurve.getYDataAsPrimitive(); double[] x = interpolatedCurve.getXDataAsPrimitive(); int nbNode = y.length; MultipleCurrencyParameterSensitivity ps0 = _psc.calculateSensitivity(instrument, multicurve); DoubleMatrix1D ps0Mat = ps0.getSensitivity(name, ccy); double[] ps0Array = ps0Mat.getData(); MultipleCurrencyParameterSensitivity[] psShift = new MultipleCurrencyParameterSensitivity[nbNode]; double[][] gammaArray = new double[nbNode][nbNode]; for (int loopnode = 0; loopnode < nbNode; loopnode++) { double[] parametersBumped = y.clone(); parametersBumped[loopnode] += _shift; YieldAndDiscountCurve curveBumped; if (isZc) { curveBumped = new YieldCurve(name, new InterpolatedDoublesCurve(x, parametersBumped, interpolatedCurve.getInterpolator(), true)); } else { curveBumped = new DiscountCurve(name, new InterpolatedDoublesCurve(x, parametersBumped, interpolatedCurve.getInterpolator(), true)); } MulticurveProviderDiscount multicurveBumped = new MulticurveProviderDiscount(); multicurveBumped.setForexMatrix(multicurve.getFxRates()); for (Currency loopccy : multicurve.getCurrencies()) { multicurveBumped.setCurve(loopccy, curveBumped); } for (IborIndex loopibor : multicurve.getIndexesIbor()) { multicurveBumped.setCurve(loopibor, curveBumped); } for (IndexON loopon : multicurve.getIndexesON()) { multicurveBumped.setCurve(loopon, curveBumped); } psShift[loopnode] = _psc.calculateSensitivity(instrument, multicurveBumped); double[] psShiftArray = psShift[loopnode].getSensitivity(name, ccy).getData(); for (int loopnode2 = 0; loopnode2 < nbNode; loopnode2++) { gammaArray[loopnode][loopnode2] = (psShiftArray[loopnode2] - ps0Array[loopnode2]) / _shift; } } // Due to approximation using a finite difference approach, the matrix computed may be (slightly) non-symmetrical. // The matrix is made symmetric by copying one half on the other. for (int loopnode1 = 1; loopnode1 < nbNode; loopnode1++) { for (int loopnode2 = loopnode1; loopnode2 < nbNode; loopnode2++) { gammaArray[loopnode2][loopnode1] = gammaArray[loopnode1][loopnode2]; } } DoubleMatrix2D gammaMat = new DoubleMatrix2D(gammaArray); return gammaMat; } /** * Computes the gamma "sum-of-column" for a given instrument. See the documentation for the definition. * The curve provider should contain only one curve which should be of the * type YieldCurve with an underlying InterpolatedDoublesCurve. * @param instrument The instrument for which the cross-gamma should be computed. * @param multicurve The multi-curve provider. * @return The gamma "sum-of-columns" vector. */ public double[] calculateSumOfColumnsGamma(final InstrumentDerivative instrument, final MulticurveProviderDiscount multicurve) { ArgumentChecker.isTrue(multicurve.getAllNames().size() == 1, "provider should have only one curve for GammaSingleCurve computation"); String name = multicurve.getAllNames().iterator().next(); YieldAndDiscountCurve curve = multicurve.getCurve(name); 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"); Delta deltaShift = new Delta(multicurve, instrument, _psc); Function1D<DoubleMatrix1D, DoubleMatrix2D> gammaFn = _differentiator.differentiate(deltaShift); double[][] gamma2 = gammaFn.evaluate(new DoubleMatrix1D(new double[1])).getData(); double[] gamma = new double[gamma2.length]; for (int i = 0; i < gamma2.length; i++) { gamma[i] = gamma2[i][0]; } return gamma; } } /** * Inner class to compute the delta for a given parallel shift of the curve. */ class Delta extends Function1D<DoubleMatrix1D, DoubleMatrix1D> { private final double[] _x; private final double[] _y; private final int _nbNode; private final String _name; private final MulticurveProviderDiscount _multicurve; private final InstrumentDerivative _instrument; private final Currency _ccy; private final InterpolatedDoublesCurve _interpolatedCurve; private final ParameterSensitivityParameterCalculator<ParameterProviderInterface> _psc; public Delta(MulticurveProviderDiscount multicurve, InstrumentDerivative instrument, ParameterSensitivityParameterCalculator<ParameterProviderInterface> psc) { _multicurve = multicurve; _name = multicurve.getAllNames().iterator().next(); _instrument = instrument; _ccy = multicurve.getCurrencyForName(_name).get(0); YieldAndDiscountCurve curve = multicurve.getCurve(_name); YieldCurve yieldCurve = (YieldCurve) curve; _interpolatedCurve = (InterpolatedDoublesCurve) yieldCurve.getCurve(); _y = _interpolatedCurve.getYDataAsPrimitive(); _x = _interpolatedCurve.getXDataAsPrimitive(); _nbNode = _x.length; _psc = psc; } @Override public DoubleMatrix1D evaluate(DoubleMatrix1D s) { double shift = s.getEntry(0); final double[] yieldBumped = _y.clone(); for (int loopnode = 0; loopnode < _nbNode; loopnode++) { yieldBumped[loopnode] += shift; } final YieldAndDiscountCurve curveBumped = new YieldCurve(_name, new InterpolatedDoublesCurve(_x, yieldBumped, _interpolatedCurve.getInterpolator(), true)); MulticurveProviderDiscount multicurveBumped = new MulticurveProviderDiscount(); multicurveBumped.setForexMatrix(_multicurve.getFxRates()); for (Currency loopccy : _multicurve.getCurrencies()) { multicurveBumped.setCurve(loopccy, curveBumped); } for (IborIndex loopibor : _multicurve.getIndexesIbor()) { multicurveBumped.setCurve(loopibor, curveBumped); } for (IndexON loopon : _multicurve.getIndexesON()) { multicurveBumped.setCurve(loopon, curveBumped); } MultipleCurrencyParameterSensitivity psShift = _psc.calculateSensitivity(_instrument, multicurveBumped); double[] ps = psShift.getSensitivity(_name, _ccy).getData(); return new DoubleMatrix1D(ps); } }