All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opengamma.strata.pricer.sensitivity.CurveGammaCalculator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
 *
 * Please see distribution for license.
 */
package com.opengamma.strata.pricer.sensitivity;

import static com.opengamma.strata.collect.Guavate.toImmutableList;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Function;
import java.util.stream.IntStream;

import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Doubles;
import com.opengamma.strata.basics.currency.Currency;
import com.opengamma.strata.basics.index.Index;
import com.opengamma.strata.basics.index.PriceIndex;
import com.opengamma.strata.basics.index.RateIndex;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.collect.tuple.Pair;
import com.opengamma.strata.data.MarketDataName;
import com.opengamma.strata.market.curve.Curve;
import com.opengamma.strata.market.curve.CurveName;
import com.opengamma.strata.market.curve.LegalEntityGroup;
import com.opengamma.strata.market.curve.ParallelShiftedCurve;
import com.opengamma.strata.market.curve.RepoGroup;
import com.opengamma.strata.market.param.CrossGammaParameterSensitivities;
import com.opengamma.strata.market.param.CrossGammaParameterSensitivity;
import com.opengamma.strata.market.param.CurrencyParameterSensitivities;
import com.opengamma.strata.market.param.CurrencyParameterSensitivity;
import com.opengamma.strata.market.param.ParameterMetadata;
import com.opengamma.strata.math.impl.differentiation.FiniteDifferenceType;
import com.opengamma.strata.math.impl.differentiation.VectorFieldFirstOrderDifferentiator;
import com.opengamma.strata.pricer.DiscountFactors;
import com.opengamma.strata.pricer.SimpleDiscountFactors;
import com.opengamma.strata.pricer.ZeroRateDiscountFactors;
import com.opengamma.strata.pricer.ZeroRatePeriodicDiscountFactors;
import com.opengamma.strata.pricer.bond.ImmutableLegalEntityDiscountingProvider;
import com.opengamma.strata.pricer.bond.LegalEntityDiscountingProvider;
import com.opengamma.strata.pricer.rate.ImmutableRatesProvider;
import com.opengamma.strata.pricer.rate.RatesProvider;

/**
 * Computes the gamma-related values for the rates curve parameters.
 * 

* By default the gamma is computed using a one basis-point shift and a forward finite difference. * The results themselves are not scaled (they represent the second order derivative). *

* Reference: Interest Rate Cross-gamma for Single and Multiple Curves. OpenGamma quantitative research 15, July 14 */ public final class CurveGammaCalculator { /** * Default implementation. Finite difference is forward and the shift is one basis point (0.0001). */ public static final CurveGammaCalculator DEFAULT = new CurveGammaCalculator(FiniteDifferenceType.FORWARD, 1e-4); /** * The first order finite difference calculator. */ private final VectorFieldFirstOrderDifferentiator fd; //------------------------------------------------------------------------- /** * Obtains an instance of the finite difference calculator using forward differencing. * * @param shift the shift to be applied to the curves * @return the calculator */ public static CurveGammaCalculator ofForwardDifference(double shift) { return new CurveGammaCalculator(FiniteDifferenceType.FORWARD, shift); } /** * Obtains an instance of the finite difference calculator using central differencing. * * @param shift the shift to be applied to the curves * @return the calculator */ public static CurveGammaCalculator ofCentralDifference(double shift) { return new CurveGammaCalculator(FiniteDifferenceType.CENTRAL, shift); } /** * Obtains an instance of the finite difference calculator using backward differencing. * * @param shift the shift to be applied to the curves * @return the calculator */ public static CurveGammaCalculator ofBackwardDifference(double shift) { return new CurveGammaCalculator(FiniteDifferenceType.BACKWARD, shift); } //------------------------------------------------------------------------- /** * Create an instance of the finite difference calculator. * * @param fdType the finite difference type * @param shift the shift to be applied to the curves */ private CurveGammaCalculator(FiniteDifferenceType fdType, double shift) { this.fd = new VectorFieldFirstOrderDifferentiator(fdType, shift); } //------------------------------------------------------------------------- /** * Computes intra-curve cross gamma by applying finite difference method to curve delta. *

* This computes the intra-curve cross gamma, i.e., the second order sensitivities to individual curves. * Thus the sensitivity of a curve delta to another curve is not produced. *

* The sensitivities are computed for discount curves, and forward curves for {@code RateIndex} and {@code PriceIndex}. * This implementation works only for single currency trades. * * @param ratesProvider the rates provider * @param sensitivitiesFn the sensitivity function * @return the cross gamma */ public CrossGammaParameterSensitivities calculateCrossGammaIntraCurve( RatesProvider ratesProvider, Function sensitivitiesFn) { ImmutableRatesProvider immProv = ratesProvider.toImmutableRatesProvider(); CurrencyParameterSensitivities baseDelta = sensitivitiesFn.apply(immProv); // used to check target sensitivity exits CrossGammaParameterSensitivities result = CrossGammaParameterSensitivities.empty(); // discount curve for (Entry entry : immProv.getDiscountCurves().entrySet()) { Currency currency = entry.getKey(); Curve curve = entry.getValue(); if (baseDelta.findSensitivity(curve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( curve, currency, c -> immProv.toBuilder().discountCurve(currency, c).build(), sensitivitiesFn); result = result.combinedWith(gammaSingle); } else if (curve.split().size() > 1) { ImmutableList curves = curve.split(); int nCurves = curves.size(); for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); if (baseDelta.findSensitivity(underlyingCurve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( underlyingCurve, currency, c -> immProv.toBuilder().discountCurve(currency, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn); result = result.combinedWith(gammaSingle); } } } } // forward curve for (Entry entry : immProv.getIndexCurves().entrySet()) { Index index = entry.getKey(); if (index instanceof RateIndex || index instanceof PriceIndex) { Currency currency = getCurrency(index); Curve curve = entry.getValue(); if (baseDelta.findSensitivity(curve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( curve, currency, c -> immProv.toBuilder().indexCurve(index, c).build(), sensitivitiesFn); result = result.combinedWith(gammaSingle); } else if (curve.split().size() > 1) { ImmutableList curves = curve.split(); int nCurves = curves.size(); for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); if (baseDelta.findSensitivity(underlyingCurve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( underlyingCurve, currency, c -> immProv.toBuilder().indexCurve(index, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn); result = result.combinedWith(gammaSingle); } } } } } return result; } //------------------------------------------------------------------------- /** * Computes intra-curve cross gamma for bond curves by applying finite difference method to curve delta. *

* This computes the intra-curve cross gamma, i.e., the second order sensitivities to individual curves. * Thus the sensitivity of a curve delta to another curve is not produced. *

* The underlying instruments must be single-currency, i.e., the curve currency must be the same as the sensitivity currency. * * @param ratesProvider the rates provider * @param sensitivitiesFn the sensitivity function * @return the cross gamma */ public CrossGammaParameterSensitivities calculateCrossGammaIntraCurve( LegalEntityDiscountingProvider ratesProvider, Function sensitivitiesFn) { LocalDate valuationDate = ratesProvider.getValuationDate(); ImmutableLegalEntityDiscountingProvider immProv = ratesProvider.toImmutableLegalEntityDiscountingProvider(); CurrencyParameterSensitivities baseDelta = sensitivitiesFn.apply(immProv); // used to check target sensitivity exits CrossGammaParameterSensitivities result = CrossGammaParameterSensitivities.empty(); // issuer curve for (Entry, DiscountFactors> entry : immProv.getIssuerCurves().entrySet()) { Pair legCcy = entry.getKey(); Currency currency = legCcy.getSecond(); Curve curve = getCurve(entry.getValue()); CurveName curveName = curve.getName(); if (baseDelta.findSensitivity(curveName, currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( curveName, curve, currency, c -> replaceIssuerCurve(immProv, legCcy, DiscountFactors.of(currency, valuationDate, c)), sensitivitiesFn); result = result.combinedWith(gammaSingle); } else { ImmutableList curves = curve.split(); int nCurves = curves.size(); if (nCurves > 1) { for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); CurveName underlyingCurveName = underlyingCurve.getName(); if (baseDelta.findSensitivity(underlyingCurveName, currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( underlyingCurveName, underlyingCurve, currency, c -> replaceIssuerCurve( immProv, legCcy, DiscountFactors.of(currency, valuationDate, curve.withUnderlyingCurve(currentIndex, c))), sensitivitiesFn); result = result.combinedWith(gammaSingle); } } } } } // repo curve for (Entry, DiscountFactors> entry : immProv.getRepoCurves().entrySet()) { Pair rgCcy = entry.getKey(); Currency currency = rgCcy.getSecond(); Curve curve = getCurve(entry.getValue()); CurveName curveName = curve.getName(); if (baseDelta.findSensitivity(curveName, currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( curveName, curve, currency, c -> replaceRepoCurve(immProv, rgCcy, DiscountFactors.of(currency, valuationDate, c)), sensitivitiesFn); result = result.combinedWith(gammaSingle); } else { ImmutableList curves = curve.split(); int nCurves = curves.size(); if (nCurves > 1) { for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); CurveName underlyingCurveName = underlyingCurve.getName(); if (baseDelta.findSensitivity(underlyingCurveName, rgCcy.getSecond()).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( underlyingCurveName, underlyingCurve, currency, c -> replaceRepoCurve( immProv, rgCcy, DiscountFactors.of(currency, valuationDate, curve.withUnderlyingCurve(currentIndex, c))), sensitivitiesFn); result = result.combinedWith(gammaSingle); } } } } } return result; } //------------------------------------------------------------------------- /** * Computes cross-curve gamma by applying finite difference method to curve delta. *

* This computes the cross-curve gamma, i.e., the second order sensitivities to full curves. * Thus the sensitivities of curve delta to other curves are produced. *

* The sensitivities are computed for discount curves, and forward curves for {@code RateIndex} and {@code PriceIndex}. * This implementation works only for single currency trades. * * @param ratesProvider the rates provider * @param sensitivitiesFn the sensitivity function * @return the cross gamma */ public CrossGammaParameterSensitivities calculateCrossGammaCrossCurve( RatesProvider ratesProvider, Function sensitivitiesFn) { ImmutableRatesProvider immProv = ratesProvider.toImmutableRatesProvider(); CurrencyParameterSensitivities baseDelta = sensitivitiesFn.apply(immProv); // used to check target sensitivity exits. CrossGammaParameterSensitivities result = CrossGammaParameterSensitivities.empty(); for (CurrencyParameterSensitivity baseDeltaSingle : baseDelta.getSensitivities()) { CrossGammaParameterSensitivities resultInner = CrossGammaParameterSensitivities.empty(); // discount curve for (Entry entry : immProv.getDiscountCurves().entrySet()) { Currency currency = entry.getKey(); Curve curve = entry.getValue(); if (baseDelta.findSensitivity(curve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( baseDeltaSingle, curve, c -> immProv.toBuilder().discountCurve(currency, c).build(), sensitivitiesFn); resultInner = resultInner.combinedWith(gammaSingle); } else if (curve.split().size() > 1) { ImmutableList curves = curve.split(); int nCurves = curves.size(); for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); if (baseDelta.findSensitivity(underlyingCurve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( baseDeltaSingle, underlyingCurve, c -> immProv.toBuilder().discountCurve(currency, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn); resultInner = resultInner.combinedWith(gammaSingle); } } } } // forward curve for (Entry entry : immProv.getIndexCurves().entrySet()) { Index index = entry.getKey(); if (index instanceof RateIndex || index instanceof PriceIndex) { Currency currency = getCurrency(index); Curve curve = entry.getValue(); if (baseDelta.findSensitivity(curve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( baseDeltaSingle, curve, c -> immProv.toBuilder().indexCurve(index, c).build(), sensitivitiesFn); resultInner = resultInner.combinedWith(gammaSingle); } else if (curve.split().size() > 1) { ImmutableList curves = curve.split(); int nCurves = curves.size(); for (int i = 0; i < nCurves; ++i) { int currentIndex = i; Curve underlyingCurve = curves.get(currentIndex); if (baseDelta.findSensitivity(underlyingCurve.getName(), currency).isPresent()) { CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve( baseDeltaSingle, underlyingCurve, c -> immProv.toBuilder().indexCurve(index, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn); resultInner = resultInner.combinedWith(gammaSingle); } } } } } result = result.combinedWith(combineSensitivities(baseDeltaSingle, resultInner)); } return result; } //------------------------------------------------------------------------- private Currency getCurrency(Index index) { if (index instanceof RateIndex) { return ((RateIndex) index).getCurrency(); } else if (index instanceof PriceIndex) { return ((PriceIndex) index).getCurrency(); } throw new IllegalArgumentException("unsupported index"); } // compute the second order sensitivity to Curve CrossGammaParameterSensitivity computeGammaForCurve( Curve curve, Currency sensitivityCurrency, Function ratesProviderFn, Function sensitivitiesFn) { Function function = new Function() { @Override public DoubleArray apply(DoubleArray t) { Curve newCurve = replaceParameters(curve, t); ImmutableRatesProvider newRates = ratesProviderFn.apply(newCurve); CurrencyParameterSensitivities sensiMulti = sensitivitiesFn.apply(newRates); return sensiMulti.getSensitivity(newCurve.getName(), sensitivityCurrency).getSensitivity(); } }; int nParams = curve.getParameterCount(); DoubleMatrix sensi = fd.differentiate(function).apply(DoubleArray.of(nParams, n -> curve.getParameter(n))); List metadata = IntStream.range(0, nParams) .mapToObj(i -> curve.getParameterMetadata(i)) .collect(toImmutableList()); return CrossGammaParameterSensitivity.of(curve.getName(), metadata, sensitivityCurrency, sensi); } // computes the sensitivity of baseDeltaSingle to Curve CrossGammaParameterSensitivity computeGammaForCurve( CurrencyParameterSensitivity baseDeltaSingle, Curve curve, Function ratesProviderFn, Function sensitivitiesFn) { Function function = new Function() { @Override public DoubleArray apply(DoubleArray t) { Curve newCurve = replaceParameters(curve, t); ImmutableRatesProvider newRates = ratesProviderFn.apply(newCurve); CurrencyParameterSensitivities sensiMulti = sensitivitiesFn.apply(newRates); return sensiMulti.getSensitivity(baseDeltaSingle.getMarketDataName(), baseDeltaSingle.getCurrency()).getSensitivity(); } }; int nParams = curve.getParameterCount(); DoubleMatrix sensi = fd.differentiate(function).apply(DoubleArray.of(nParams, n -> curve.getParameter(n))); List metadata = IntStream.range(0, nParams) .mapToObj(i -> curve.getParameterMetadata(i)) .collect(toImmutableList()); return CrossGammaParameterSensitivity.of( baseDeltaSingle.getMarketDataName(), baseDeltaSingle.getParameterMetadata(), curve.getName(), metadata, baseDeltaSingle.getCurrency(), sensi); } private CrossGammaParameterSensitivity combineSensitivities( CurrencyParameterSensitivity baseDeltaSingle, CrossGammaParameterSensitivities blockCrossGamma) { double[][] valuesTotal = new double[baseDeltaSingle.getParameterCount()][]; List, List>> order = new ArrayList<>(); for (int i = 0; i < baseDeltaSingle.getParameterCount(); ++i) { ArrayList innerList = new ArrayList<>(); for (CrossGammaParameterSensitivity gammaSingle : blockCrossGamma.getSensitivities()) { innerList.addAll(gammaSingle.getSensitivity().row(i).toList()); if (i == 0) { order.add(gammaSingle.getOrder().get(0)); } } valuesTotal[i] = Doubles.toArray(innerList); } return CrossGammaParameterSensitivity.of( baseDeltaSingle.getMarketDataName(), baseDeltaSingle.getParameterMetadata(), order, baseDeltaSingle.getCurrency(), DoubleMatrix.ofUnsafe(valuesTotal)); } //------------------------------------------------------------------------- /** * Computes the "sum-of-column gamma" or "semi-parallel gamma" for a sensitivity function. *

* This implementation supports a single {@link Curve} on the zero-coupon rates. * By default the gamma is computed using a one basis-point shift and a forward finite difference. * The results themselves are not scaled (they represent the second order derivative). * * @param curve the single curve to be bumped * @param curveCurrency the currency of the curve and resulting sensitivity * @param sensitivitiesFn the function to convert the bumped curve to parameter sensitivities * @return the "sum-of-columns" or "semi-parallel" gamma vector */ public CurrencyParameterSensitivity calculateSemiParallelGamma( Curve curve, Currency curveCurrency, Function sensitivitiesFn) { Delta deltaShift = new Delta(curve, sensitivitiesFn); Function gammaFn = fd.differentiate(deltaShift); DoubleArray gamma = gammaFn.apply(DoubleArray.filled(1)).column(0); return curve.createParameterSensitivity(curveCurrency, gamma); } //------------------------------------------------------------------------- private Curve replaceParameters(Curve curve, DoubleArray newParameters) { return curve.withPerturbation((i, v, m) -> newParameters.get(i)); } //------------------------------------------------------------------------- /** * Inner class to compute the delta for a given parallel shift of the curve. */ static class Delta implements Function { private final Curve curve; private final Function sensitivitiesFn; Delta(Curve curve, Function sensitivitiesFn) { this.curve = curve; this.sensitivitiesFn = sensitivitiesFn; } @Override public DoubleArray apply(DoubleArray s) { double shift = s.get(0); Curve curveBumped = ParallelShiftedCurve.absolute(curve, shift); CurrencyParameterSensitivity pts = sensitivitiesFn.apply(curveBumped); return pts.getSensitivity(); } } //------------------------------------------------------------------------- private Curve getCurve(DiscountFactors discountFactors) { if (discountFactors instanceof SimpleDiscountFactors) { return ((SimpleDiscountFactors) discountFactors).getCurve(); } if (discountFactors instanceof ZeroRateDiscountFactors) { return ((ZeroRateDiscountFactors) discountFactors).getCurve(); } if (discountFactors instanceof ZeroRatePeriodicDiscountFactors) { return ((ZeroRatePeriodicDiscountFactors) discountFactors).getCurve(); } throw new IllegalArgumentException("Unsupported DiscountFactors type"); } private CrossGammaParameterSensitivity computeGammaForCurve( CurveName curveName, Curve curve, Currency sensitivityCurrency, Function ratesProviderFn, Function sensitivitiesFn) { Function function = new Function() { @Override public DoubleArray apply(DoubleArray t) { Curve newCurve = curve.withPerturbation((i, v, m) -> t.get(i)); ImmutableLegalEntityDiscountingProvider newRates = ratesProviderFn.apply(newCurve); CurrencyParameterSensitivities sensiMulti = sensitivitiesFn.apply(newRates); return sensiMulti.getSensitivity(curveName, sensitivityCurrency).getSensitivity(); } }; int nParams = curve.getParameterCount(); DoubleMatrix sensi = fd.differentiate(function).apply(DoubleArray.of(nParams, n -> curve.getParameter(n))); List metadata = IntStream.range(0, nParams) .mapToObj(i -> curve.getParameterMetadata(i)) .collect(toImmutableList()); return CrossGammaParameterSensitivity.of(curveName, metadata, sensitivityCurrency, sensi); } private ImmutableLegalEntityDiscountingProvider replaceIssuerCurve( ImmutableLegalEntityDiscountingProvider ratesProvider, Pair legCcy, DiscountFactors discountFactors) { Map, DiscountFactors> curves = new HashMap<>(); curves.putAll(ratesProvider.getIssuerCurves()); curves.put(legCcy, discountFactors); return ratesProvider.toBuilder() .issuerCurves(curves) .build(); } private ImmutableLegalEntityDiscountingProvider replaceRepoCurve( ImmutableLegalEntityDiscountingProvider ratesProvider, Pair rgCcy, DiscountFactors discountFactors) { Map, DiscountFactors> curves = new HashMap<>(); curves.putAll(ratesProvider.getRepoCurves()); curves.put(rgCcy, discountFactors); return ratesProvider.toBuilder() .repoCurves(curves) .build(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy