Package com.opengamma.analytics.financial.equity

Source Code of com.opengamma.analytics.financial.equity.EquityDerivativeSensitivityCalculator

/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.equity;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.equity.option.EquityIndexOption;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitor;
import com.opengamma.analytics.financial.interestrate.NodeYieldSensitivityCalculator;
import com.opengamma.analytics.financial.interestrate.PresentValueNodeSensitivityCalculator;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.GeneralSmileInterpolator;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SurfaceArrayUtils;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.sabr.SmileSurfaceDataBundle;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneynessFcnBackedByGrid;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurfaceInterpolator;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface;
import com.opengamma.analytics.math.surface.InterpolatedSurfaceAdditiveShiftFunction;
import com.opengamma.analytics.math.surface.NodalDoublesSurface;
import com.opengamma.analytics.math.surface.Surface;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.DoublesPair;
import com.opengamma.util.tuple.Triple;

/**
* This Calculator provides simple bump and reprice sensitivities for Derivatives
*/
@SuppressWarnings("deprecation")
public class EquityDerivativeSensitivityCalculator {
  /** The default value of the absolute shift */
  private static final double DEFAULT_ABS_SHIFT = 0.0001; // Shift used for vol, +/- 1bp == 0.01%
  /** The default value of the relative shift */
  private static final double DEFAULT_REL_SHIFT = 0.01; // Shift used for rates  +/- 1% * Rate
  /** Gets the settlement time for the instrument */
  private static final SettlementTimeCalculator SETTLEMENT_CALCULATOR = SettlementTimeCalculator.getInstance();
  /** The pricer */
  private final InstrumentDerivativeVisitor<StaticReplicationDataBundle, Double> _pricer;

  /**
   * @param pricer The pricer, not null
   */
  public EquityDerivativeSensitivityCalculator(final InstrumentDerivativeVisitor<StaticReplicationDataBundle, Double> pricer) {
    ArgumentChecker.notNull(pricer, "pricer");
    _pricer = pricer;
  }

  /**
   * This calculates the sensitivity of the present value (PV) to a unit move in the forward.
   * The volatility surface remains unchanged.
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @param relShift Relative size of shift made in centered-finite difference approximation.
   * @return A Double. Currency amount per unit amount change in the black volatility
   */
  public Double calcForwardSensitivity(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final double relShift) {
    ArgumentChecker.notNull(derivative, "null EquityDerivative");
    ArgumentChecker.notNull(market, "null EquityOptionDataBundle");

    // Shift UP
    StaticReplicationDataBundle bumpedMarket = new StaticReplicationDataBundle(market.getVolatilitySurface(), market.getDiscountCurve(), market.getForwardCurve()
        .withFractionalShift(relShift));
    final double pvUp = derivative.accept(_pricer, bumpedMarket);

    // Shift Down
    bumpedMarket = new StaticReplicationDataBundle(market.getVolatilitySurface(), market.getDiscountCurve(), market.getForwardCurve().withFractionalShift(-relShift));
    final double pvDown = derivative.accept(_pricer, bumpedMarket);

    final double t = derivative.accept(SETTLEMENT_CALCULATOR);
    final double fwd = market.getForwardCurve().getForward(t);
    // Centered-difference result
    return (pvUp - pvDown) / 2.0 / relShift / fwd;
  }

  /**
   * This calculates the sensitivity of the present value (PV) to a unit move in the forward,
   * under a default shift of 1% of the forward <p>
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A Double. Currency amount per unit amount change in the black volatility
   */
  public Double calcForwardSensitivity(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    return calcForwardSensitivity(derivative, market, DEFAULT_REL_SHIFT);
  }

  /**
   * Calculates the sensitivity of the present value (PV) to a change in the funding rate from valuation to settlement.
   * Also know as PVBP and DV01, though note this return per UNIT change in rate. calcPV01 returns per basis point change in rates.  <p>
   * <p>
   * Rates enter the pricing of a EquityDerivative in two places: in the discounting and forward projection.<p>
   * The presentValue has been structured such that the form of the PV = Z(t,T) * FwdPrice(t,T) with Z a zero coupon bond, and t and T the valuation and settlement times respectively.
   * The form of our discounting rates is such that Z(t,T) = exp[- R(t,T) * (T-t)], hence  dZ/dR = -(T-t)*Z(t,T) and d(PV)/dR = PV * dZ/dR
   * The forward's dependence on the discounting rate is similar to the zero coupon bonds, but of opposite sign, dF/dR = (T-t)*F(t,T)
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @param shift Relative size of shift made in centered-finite difference approximation.
   * @return A Double in the currency, deriv.getCurrency(). Currency amount per unit amount change in discount rate
   */
  public Double calcDiscountRateSensitivity(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final double shift) {
    ArgumentChecker.notNull(market, "market");
    ArgumentChecker.notNull(derivative, "derivative");
    // Sensitivity from the discounting

    final double pv = derivative.accept(_pricer, market);
    final double timeToSettlement = derivative.accept(SETTLEMENT_CALCULATOR);

    // Sensitivity from forward projection
    final double fwdSens = calcForwardSensitivity(derivative, market, shift);
    final double fwd = market.getForwardCurve().getForward(timeToSettlement);

    return timeToSettlement * (fwd * fwdSens - pv);
  }

  /**
   * Calculates the sensitivity of the present value (PV) to a change in the funding rate from valuation to settlement.
   * Also know as PVBP and DV01, though note this return per UNIT change in rate. calcPV01 returns per basis point change in rates.  <p>
   * <p>
   * Rates enter the pricing of a EquityDerivative in two places: in the discounting and forward projection.<p>
   * The presentValue has been structured such that the form of the PV = Z(t,T) * FwdPrice(t,T) with Z a zero coupon bond, and t and T the valuation and settlement times respectively.
   * The form of our discounting rates is such that Z(t,T) = exp[- R(t,T) * (T-t)], hence  dZ/dR = (t-T)*Z(t,T) and d(PV)/dR = PV * dZ/dR
   * The forward's dependence on the discounting rate is similar to the zero coupon bonds, but of opposite sign, dF/dR = (T-t)*F(t,T)
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A Double in the currency, deriv.getCurrency(). Currency amount per unit amount change in discount rate
   */
  public Double calcDiscountRateSensitivity(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    return calcDiscountRateSensitivity(derivative, market, DEFAULT_REL_SHIFT);
  }

  /**
   * Calculates the sensitivity of the present value (PV) to a basis point (bp) move in the funding rates across all maturities. <p>
   * Also know as PVBP and DV01.
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A Double in the currency, derivative.getCurrency()
   */
  public Double calcPV01(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    return calcDiscountRateSensitivity(derivative, market) / 10000;
  }

  /**
   * This calculates the sensitivity of the present value (PV) to the continuously-compounded discount rates at the knot points of the funding curve. <p>
   * The return format is a DoubleMatrix1D (i.e. a vector) with length equal to the total number of knots in the curve <p>
   * The change of a curve due to the movement of a single knot is interpolator-dependent, so an instrument can have sensitivity to knots at times beyond its maturity
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A DoubleMatrix1D containing bucketed delta in order and length of market.getDiscountCurve(). Currency amount per unit amount change in discount rate
   */
  public DoubleMatrix1D calcDeltaBucketed(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    ArgumentChecker.notNull(derivative, "null EquityDerivative");
    ArgumentChecker.notNull(market, "null EquityOptionDataBundle");

    // We know that the EquityDerivative only has true sensitivity to one maturity on one curve.
    // A function written for interestRate sensitivities spreads this sensitivity across yield nodes
    // NodeSensitivityCalculator.curveToNodeSensitivities(curveSensitivities, interpolatedCurves)

    if (!(market.getDiscountCurve() instanceof YieldCurve)) {
      throw new IllegalArgumentException("Can only handle YieldCurve");
    }
    final YieldCurve discCrv = (YieldCurve) market.getDiscountCurve();

    final double settlement = derivative.accept(SETTLEMENT_CALCULATOR);
    final double sens = calcDiscountRateSensitivity(derivative, market);

    final NodeYieldSensitivityCalculator distributor = PresentValueNodeSensitivityCalculator.getDefaultInstance();
    final List<Double> result = distributor.curveToNodeSensitivity(Arrays.asList(DoublesPair.of(settlement, sens)), discCrv);
    return new DoubleMatrix1D(result.toArray(new Double[result.size()]));
  }

  /**
   * This calculates the sensitivity of the present value (PV) to the lognormal Black implied volatilities at the knot points of the surface. <p>
   * Note - the change of the surface due to the movement of a single node is interpolator-dependent, so an instrument may have non-local sensitivity
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A Double. Currency amount per unit amount change in the black volatility
   */
  public Double calcBlackVegaParallel(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    return calcBlackVegaParallel(derivative, market, DEFAULT_ABS_SHIFT);
  }

  /**
   * This calculates the sensitivity of the present value (PV) to the lognormal Black implied volatilities at the knot points of the surface. <p>
   * Note - the change of the surface due to the movement of a single node is interpolator-dependent, so an instrument may have non-local sensitivity
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @param shift Size of shift made in centered-finite difference approximation. e.g. 1% would be 0.01, and 1bp 0.0001
   * @return A Double. Currency amount per unit amount change in the black volatility
   */
  public Double calcBlackVegaParallel(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final double shift) {
    ArgumentChecker.notNull(derivative, "null EquityDerivative");
    ArgumentChecker.notNull(market, "null EquityOptionDataBundle");

    // Parallel shift UP
    final BlackVolatilitySurface<?> upSurface = market.getVolatilitySurface().withShift(shift, true);
    final double pvUp = derivative.accept(_pricer, new StaticReplicationDataBundle(upSurface, market.getDiscountCurve(), market.getForwardCurve()));

    // Parallel shift DOWN
    final BlackVolatilitySurface<?> downSurface = market.getVolatilitySurface().withShift(-shift, true);
    final double pvDown = derivative.accept(_pricer, new StaticReplicationDataBundle(downSurface, market.getDiscountCurve(), market.getForwardCurve()));

    // Centered-difference result
    return (pvUp - pvDown) / (2.0 * shift);
  }

  /**
   * This calculates the sensitivity of the present value (PV) to the lognormal Black implied volatilities at the knot points of the surface. <p>
   * The return format is a DoubleMatrix2D with rows equal to the total number of maturities and columns equal to the number of strikes. <p>
   * Note - the change of the surface due to the movement of a single node is interpolator-dependent, so an instrument may have non-local sensitivity
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @return A NodalDoublesSurface with same axes as market.getVolatilitySurface(). Contains currency amount per unit amount change in the black volatility of each node
   */
  public NodalDoublesSurface calcBlackVegaForEntireSurface(final InstrumentDerivative derivative, final StaticReplicationDataBundle market) {
    return calcBlackVegaForEntireSurface(derivative, market, DEFAULT_ABS_SHIFT);
  }

  /**
   * This calculates the sensitivity of the present value (PV) to the lognormal Black implied volatilities at the knot points of the surface. <p>
   * The return format is a DoubleMatrix2D with rows equal to the total number of maturities and columns equal to the number of strikes. <p>
   * Note - the change of the surface due to the movement of a single node is interpolator-dependent, so an instrument may have non-local sensitivity
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @param shift Size of shift made in centred-finite difference approximation. e.g. 1% would be 0.01, and 1bp 0.0001
   * @return A NodalDoublesSurface with same axes as market.getVolatilitySurface(). Contains currency amount per unit amount change in the black volatility of each node
   */
  public NodalDoublesSurface calcBlackVegaForEntireSurface(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final double shift) {
    ArgumentChecker.notNull(derivative, "null EquityDerivative");
    ArgumentChecker.notNull(market, "null EquityOptionDataBundle");

    if (market.getVolatilitySurface().getSurface() instanceof InterpolatedDoublesSurface) {
      final InterpolatedDoublesSurface blackSurf = (InterpolatedDoublesSurface) market.getVolatilitySurface().getSurface();
      final Double[] maturities = blackSurf.getXData();
      final Double[] strikes = blackSurf.getYData();
      final int nNodes = maturities.length;
      ArgumentChecker.isTrue(nNodes == strikes.length, "Number of strikes must match number of nodes");
      // Bump and reprice
      final Double[] vegas = new Double[nNodes];
      for (int j = 0; j < nNodes; j++) {
        vegas[j] = calcBlackVegaForSinglePoint(derivative, market, maturities[j], strikes[j], shift);
      }
      return NodalDoublesSurface.from(maturities, strikes, vegas);

      // Special case for EquityIndexOptions
    } else if (market.getVolatilitySurface() instanceof BlackVolatilitySurfaceMoneynessFcnBackedByGrid) {
      final BlackVolatilitySurfaceMoneynessFcnBackedByGrid surfaceBundle = (BlackVolatilitySurfaceMoneynessFcnBackedByGrid) market.getVolatilitySurface();

      final EquityIndexOption option;
      if (derivative instanceof EquityIndexOption) {
        option = (EquityIndexOption) derivative;
      } else {
        throw new OpenGammaRuntimeException("Calculator with BlackVolatilitySurfaceMoneynessFcnBackedByGrid was expecting an EquityIndexOption.");
      }
      // Unpack
      final SmileSurfaceDataBundle volGrid = surfaceBundle.getGridData();
      final double[] forwards = volGrid.getForwards();
      final double[] volExpiries = volGrid.getExpiries();
      final int nExpiries = volGrid.getNumExpiries();
      final double[][] strikes = volGrid.getStrikes();
      final double[][] vols = volGrid.getVolatilities();
      final VolatilitySurfaceInterpolator surfaceInterpolator = surfaceBundle.getInterpolator();
      final GeneralSmileInterpolator strikeInterpolator = surfaceInterpolator.getSmileInterpolator();

      // Base price and set of independent smile fits (one function vol(k) for each expiry)
      final Double pvBase = option.accept(_pricer, market);
      final Function1D<Double, Double>[] smileFitsBase = surfaceInterpolator.getIndependentSmileFits(volGrid);

      // Bump and reprice - loop over expiry and strike
      final List<Triple<Double, Double, Double>> triplesExpiryStrikeVega = new ArrayList<>();
      // TODO: REVIEW: We can drastically reduce the time it takes to compute this if we are sensible about avoiding points which almost certainly won't have any sensitivity
      // Of course, this is all based upon the interpolor's scheme...
      final int expiryIndex = SurfaceArrayUtils.getLowerBoundIndex(volExpiries, option.getTimeToExpiry());
      for (int t = Math.max(0, expiryIndex - 3); t < Math.min(nExpiries, expiryIndex + 4); t++) {
        final int nStrikes = strikes[t].length;
        final int strikeIndex = SurfaceArrayUtils.getLowerBoundIndex(strikes[t], option.getStrike());
        for (int k = Math.max(0, strikeIndex - 6); k < Math.min(nStrikes, strikeIndex + 7); k++) {
          // TODO: REVIEW We only recompute the smile function for the specific expiry we are bumping..
          final double[] bumpedVols = Arrays.copyOf(vols[t], nStrikes);
          bumpedVols[k] = vols[t][k] - shift;
          final Function1D<Double, Double> thisExpirysSmile = strikeInterpolator.getVolatilityFunction(forwards[t], strikes[t], volExpiries[t], bumpedVols);
          final Function1D<Double, Double>[] scenarioSmileFits = Arrays.copyOf(smileFitsBase, smileFitsBase.length);
          scenarioSmileFits[t] = thisExpirysSmile;
          final BlackVolatilitySurfaceMoneynessFcnBackedByGrid shiftedSurface = surfaceInterpolator.combineIndependentSmileFits(scenarioSmileFits, volGrid);
          //BlackVolatilitySurfaceMoneynessFcnBackedByGrid shiftedSurface = surfaceInterpolator.getBumpedVolatilitySurface(volGrid, t, k, -shift);
          final StaticReplicationDataBundle shiftedMarket = market.withShiftedSurface(shiftedSurface);
          final Double pvScenario = option.accept(_pricer, shiftedMarket);

          ArgumentChecker.notNull(pvScenario, "Null PV in shifted scenario, T = " + volExpiries[t] + ", k = " + strikes[t][k]);
          final Double vega = (pvScenario - pvBase) / -shift;
          final Triple<Double, Double, Double> xyz = new Triple<>(volExpiries[t], strikes[t][k], vega);
          triplesExpiryStrikeVega.add(xyz);
        }
      }
      return NodalDoublesSurface.from(triplesExpiryStrikeVega);

    } else {
      throw new OpenGammaRuntimeException("Currently will only accept an Equity Volatility Surface based on an InterpolatedDoublesSurface, "
          + "or BlackVolatilitySurfaceMoneynessFcnBackedByGrid");
    }
  }

  /**
   * Compute the price sensitivity to a shift of the Black volatility at a given maturity and strike. <p>
   * Note - the change of the surface due to the movement of a single node is interpolator-dependent, so an instrument may have non-local sensitivity.<p>
   * Important!!! If the <i>(x, y)</i> value(s) of the shift(s) are not in the nodal points of the original surface, they are added (with shift) to the nodal points of the new surface.
   * @param derivative the EquityDerivative
   * @param market the EquityOptionDataBundle
   * @param maturity a double in same unit as VolatilitySurface
   * @param strike a double in same unit as VolatilitySurface
   * @param shift Size of shift made in centered-finite difference approximation. e.g. 1% would be 0.01, and 1bp 0.0001
   * @return Currency amount per unit amount change in the black volatility at the point provided
   */
  public double calcBlackVegaForSinglePoint(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final double maturity, final double strike,
      final double shift) {

    final Surface<Double, Double, Double> surface = market.getVolatilitySurface().getSurface();
    ArgumentChecker.isTrue(surface instanceof InterpolatedDoublesSurface, "Currently will only accept a Equity VolatilitySurfaces based on an InterpolatedDoublesSurface");

    final InterpolatedDoublesSurface blackSurf = (InterpolatedDoublesSurface) surface;
    final InterpolatedSurfaceAdditiveShiftFunction volShifter = new InterpolatedSurfaceAdditiveShiftFunction();

    // shift UP
    final InterpolatedDoublesSurface bumpedVolUp = volShifter.evaluate(blackSurf, maturity, strike, shift);
    StaticReplicationDataBundle bumpedMarket = new StaticReplicationDataBundle(market.getVolatilitySurface().withSurface(bumpedVolUp), market.getDiscountCurve(),
        market.getForwardCurve());
    final double pvUp = derivative.accept(_pricer, bumpedMarket);

    // shift DOWN
    final InterpolatedDoublesSurface bumpedVolDown = volShifter.evaluate(blackSurf, maturity, strike, -shift);
    bumpedMarket = new StaticReplicationDataBundle(market.getVolatilitySurface().withSurface(bumpedVolDown), market.getDiscountCurve(), market.getForwardCurve());
    final double pvDown = derivative.accept(_pricer, bumpedMarket);

    // Centered-difference result
    return (pvUp - pvDown) / (2.0 * shift);
  }

}
TOP

Related Classes of com.opengamma.analytics.financial.equity.EquityDerivativeSensitivityCalculator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.