Package com.opengamma.analytics.financial.model.finitedifference

Source Code of com.opengamma.analytics.financial.model.finitedifference.SABRFiniteDifferenceTest

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

import static org.testng.AssertJUnit.assertEquals;

import org.testng.annotations.Test;

import com.opengamma.analytics.financial.model.finitedifference.applications.InitialConditionsProvider;
import com.opengamma.analytics.financial.model.finitedifference.applications.PDE1DCoefficientsProvider;
import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.BlackFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.BlackImpliedVolatilityFormula;
import com.opengamma.analytics.financial.model.volatility.local.DupireLocalVolatilityCalculator;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceConverter;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceMoneyness;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.smile.function.SABRFormulaData;
import com.opengamma.analytics.financial.model.volatility.smile.function.SABRHaganVolatilityFunction;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneyness;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.surface.PriceSurface;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.DoubleQuadraticInterpolator1D;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDoubleQuadraticDataBundle;
import com.opengamma.analytics.math.surface.FunctionalDoublesSurface;

/**
*
*/
@Test
public class SABRFiniteDifferenceTest {
  private static final boolean DEBUG = false; //set to false before commit

  private static final DoubleQuadraticInterpolator1D INTERPOLATOR_1D = new DoubleQuadraticInterpolator1D();
  //private static final PDEDataBundleProvider PDE_DATA_PROVIDER = new PDEDataBundleProvider();
  private static final PDE1DCoefficientsProvider PDE_PROVIDER = new PDE1DCoefficientsProvider();
  private static final InitialConditionsProvider INITIAL_CONDITIONS_PROVIDER = new InitialConditionsProvider();

  private static final BlackImpliedVolatilityFormula BLACK_IMPLIED_VOL = new BlackImpliedVolatilityFormula();
  private static final SABRHaganVolatilityFunction SABR = new SABRHaganVolatilityFunction();
  private static final double SPOT = 0.04;
  private static final double STRIKE;
  private static final Function1D<Double, Double> ATM_VOL;
  private static final double BETA = 0.5;
  private static final double RHO = -0.6;
  // private static final double NU = 0.5;
  private static final Function1D<Double, Double> NU;
  private static final double RATE = 0.02;
  private static final double DRIFT = 0.07;
  private static final double T = 5.0;
  private static final ForwardCurve FORWARD_CURVE;
  private static final YieldAndDiscountCurve YIELD_CURVE = YieldCurve.from(ConstantDoublesCurve.from(RATE));
  private static final EuropeanVanillaOption OPTION;
  private static final ConvectionDiffusionPDE1DStandardCoefficients PDE;

  private static BoundaryCondition LOWER;
  private static BoundaryCondition UPPER;

  private static final PriceSurface SABR_PRICE_SURFACE;
  private static final BlackVolatilitySurfaceStrike SABR_VOL_SURFACE;
  private static final BlackVolatilitySurfaceMoneyness SABR_VOL_M_SURFACE;
  private static final LocalVolatilitySurfaceStrike SABR_LOCAL_VOL;
  private static final LocalVolatilitySurfaceMoneyness LV_M;
  private static final Function1D<Double, Double> PAYOFF;
  /**
   *
   */
  static {
    ATM_VOL = new Function1D<Double, Double>() {

      @Override
      public Double evaluate(final Double t) {
        return (0.05 + 0.1 * t) * Math.exp(-0.3 * t) + 0.2;
      }
    };

    NU = new Function1D<Double, Double>() {
      @Override
      public Double evaluate(final Double t) {
        return 0.3 * Math.exp(-0.2 * t) + 0.2;
      }
    };

    final Function1D<Double, Double> func = new Function1D<Double, Double>() {

      @Override
      public Double evaluate(final Double t) {
        return SPOT * Math.exp(t * DRIFT); //* (1 + 0.3 * (1 - Math.exp(-0.3 * t)));
      }
    };

    FORWARD_CURVE = new ForwardCurve(func);

    SABR_VOL_SURFACE = getSABRImpliedVolSurface(BETA, FORWARD_CURVE);

    final Function<Double, Double> sabrMSurface = new Function<Double, Double>() {

      @SuppressWarnings("synthetic-access")
      @Override
      public Double evaluate(final Double... tx) {
        final double t = tx[0];
        final double x = tx[1];
        final double f = FORWARD_CURVE.getForward(t);
        final double k = x * f;
        final double alpha = ATM_VOL.evaluate(t) * Math.pow(f, 1 - BETA);
        final double nu = NU.evaluate(t);
        final SABRFormulaData sabrdata = new SABRFormulaData(alpha, BETA, RHO, nu);
        final EuropeanVanillaOption option = new EuropeanVanillaOption(k, t, true);
        final Function1D<SABRFormulaData, Double> func1 = SABR.getVolatilityFunction(option, f);
        return func1.evaluate(sabrdata);
      }
    };

    SABR_VOL_M_SURFACE = new BlackVolatilitySurfaceMoneyness(FunctionalDoublesSurface.from(sabrMSurface), FORWARD_CURVE);

    SABR_PRICE_SURFACE = getSABRPriceSurface(BETA, FORWARD_CURVE, YIELD_CURVE);

    final DupireLocalVolatilityCalculator cal = new DupireLocalVolatilityCalculator();

    SABR_LOCAL_VOL = getSABRLocalVolSurface(BETA, FORWARD_CURVE);

    LV_M = cal.getLocalVolatility(SABR_VOL_M_SURFACE);

    STRIKE = SPOT / YIELD_CURVE.getDiscountFactor(T);

    OPTION = new EuropeanVanillaOption(STRIKE, T, true);

    LOWER = new DirichletBoundaryCondition(0, 0.0);
    UPPER = new NeumannBoundaryCondition(1.0, 5 * FORWARD_CURVE.getForward(T), false);

    PDE = PDE_PROVIDER.getBackwardsLocalVol(FORWARD_CURVE, T, SABR_LOCAL_VOL);
    PAYOFF = INITIAL_CONDITIONS_PROVIDER.getEuropeanPayoff(STRIKE, true);
  }

  /**
   * Run a backwards PDE once for the example strike and maturity, using the local volatility derived from the SABR
   * implied volatility surface, and check the price agrees with SABR.
   */
  @Test
  public void testBackwardsSingleStrike() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 20;
    final int xNodes = 101;
    final PDEGrid1D grid = new PDEGrid1D(tNodes, xNodes, T, LOWER.getLevel(), UPPER.getLevel());
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(PDE, PAYOFF, LOWER, UPPER, grid);
    final PDEResults1D res = solver.solve(db);
    final double fwd0 = FORWARD_CURVE.getForward(T);
    final double df = YIELD_CURVE.getDiscountFactor(T);
    final int i = (int) (xNodes * fwd0 / UPPER.getLevel());
    final double fwd = res.getSpaceValue(i);
    final double price = df * res.getFunctionValue(i);

    assertEquals("forward", fwd0, fwd, 1e-9);
    assertEquals("price", SABR_PRICE_SURFACE.getPrice(T, STRIKE), price, price * 2e-3);

    final BlackFunctionData data = new BlackFunctionData(FORWARD_CURVE.getForward(T), df, 0.0);

    double impVol;
    try {
      impVol = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price);
    } catch (final Exception e) {
      impVol = 0.0;
    }
    assertEquals("vol", SABR_VOL_SURFACE.getVolatility(T, STRIKE), impVol, 1e-3);
  }

  /**
   * Run a classic (i.e. defined in terms of instantaneous short rates) backwards PDE once for the example strike and maturity, using the local volatility derived from the SABR
   * implied volatility surface, and check the price agrees with SABR.
   */
  @Test
  public void testClassicBackwardsSingleStrike() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 20;
    final int xNodes = 101;
    final BoundaryCondition lower = new DirichletBoundaryCondition(0, 0.0);
    final BoundaryCondition upper = new NeumannBoundaryCondition(new Function1D<Double, Double>() {
      @Override
      public Double evaluate(final Double tau) {
        return Math.exp(-tau * (RATE - DRIFT));
      }
    }, 5 * SPOT, false);

    final PDEGrid1D grid = new PDEGrid1D(tNodes, xNodes, T, lower.getLevel(), upper.getLevel());
    final ConvectionDiffusionPDE1DCoefficients pde = PDE_PROVIDER.getBackwardsLocalVol(RATE, RATE - DRIFT, T, SABR_LOCAL_VOL);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<>(pde, PAYOFF, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);

    final double df = YIELD_CURVE.getDiscountFactor(T);
    final int i = (int) (xNodes * SPOT / upper.getLevel());
    final double s = res.getSpaceValue(i);
    final double price = res.getFunctionValue(i);

    assertEquals("spot", SPOT, s, 1e-9);
    assertEquals("price", SABR_PRICE_SURFACE.getPrice(T, STRIKE), price, price * 2e-3);

    final BlackFunctionData data = new BlackFunctionData(FORWARD_CURVE.getForward(T), df, 0.0);

    double impVol;
    try {
      impVol = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price);
    } catch (final Exception e) {
      impVol = 0.0;
    }
    assertEquals("vol", SABR_VOL_SURFACE.getVolatility(T, STRIKE), impVol, 1e-3);
  }

  /**
   * Run a backwards PDE multiple time at different strikes for maturity, using the local volatility derived from the
   * SABR implied volatility surface, and check the price agrees with SABR.
   */
  @Test
  public void testBackwardsMultipleStrikes() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;

    final double forward = FORWARD_CURVE.getForward(T);
    final double maxForward = 5.0 * forward;

    final BoundaryCondition lower = new DirichletBoundaryCondition(0.0, 0.0);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 5.0);
    final MeshingFunction spaceMesh = new HyperbolicMeshing(0.0, maxForward, forward, xNodes, 0.1);
    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);

    for (int j = 0; j < 50; j++) {
      final double strike = forward * (0.5 + 1.5 * j / 49.);
      final BoundaryCondition upper = new DirichletBoundaryCondition(maxForward - strike, maxForward);
      // final ZZConvectionDiffusionPDEDataBundle pdeData = PDE_DATA_PROVIDER.getBackwardsLocalVol(strike, T, true, SABR_LOCAL_VOL, FORWARD_CURVE);
      final Function1D<Double, Double> payoff = INITIAL_CONDITIONS_PROVIDER.getEuropeanPayoff(strike, true);
      final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(PDE, payoff, lower, upper, grid);
      final PDEResults1D res = solver.solve(db);

      //since the grid of forwards values may not contain the desired forward, we interpolate
      final double[] tFwd = new double[xNodes];
      final double[] tVol = new double[xNodes];
      int count = 0;
      for (int i = 0; i < xNodes; i++) {
        final double fwd = grid.getSpaceNode(i);
        if (fwd > 0.8 * forward && fwd < 1.2 * forward) {
          final double price = res.getFunctionValue(i);
          final double vol = BlackFormulaRepository.impliedVolatility(price, fwd, strike, T, true);
          tFwd[count] = fwd;

          tVol[count] = vol;
          count++;
        }
      }
      final double[] fwds = new double[count];
      final double[] vols = new double[count];
      System.arraycopy(tFwd, 0, fwds, 0, count);
      System.arraycopy(tVol, 0, vols, 0, count);
      final Interpolator1DDoubleQuadraticDataBundle idb = INTERPOLATOR_1D.getDataBundle(fwds, vols);
      final double intepVol = INTERPOLATOR_1D.interpolate(idb, forward);
      final double sabrVol = SABR_VOL_SURFACE.getVolatility(T, strike);
      if (DEBUG) {
        System.out.println("backwards PDE");
        System.out.println(strike + "\t" + intepVol + "\t" + sabrVol);
      } else {
        assertEquals("Backwards PDE vols", sabrVol, intepVol, 1.5e-3); //15bps error TODO why so large
      }
    }
  }

  /**
   * Run a forwards PDE once, using the local volatility derived from the
   * SABR implied volatility surface, and check the prices at the example expiry and strikes agrees with SABR.
   */
  @Test
  public void testForwardPDE() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;

    final double maxMoneyness = 6.0;

    //  final ZZConvectionDiffusionPDEDataBundle pdeData = PDE_DATA_PROVIDER.getForwardLocalVol(SABR_LOCAL_VOL, FORWARD_CURVE, true);
    final ConvectionDiffusionPDE1DCoefficients pde = PDE_PROVIDER.getForwardLocalVol(FORWARD_CURVE, SABR_LOCAL_VOL);
    final Function1D<Double, Double> initialCondition = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(true);

    final BoundaryCondition lower = new DirichletBoundaryCondition(1.0, 0.0);
    // BoundaryCondition upper = new DirichletBoundaryCondition(0.0, maxMoneyness);
    final BoundaryCondition upper = new NeumannBoundaryCondition(0, maxMoneyness, false);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 4.0);
    final MeshingFunction spaceMesh = new HyperbolicMeshing(0.0, maxMoneyness, 1.0, xNodes, 0.05);
    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);

    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<>(pde, initialCondition, lower, upper, grid);
    final PDEResults1D res = solver.solve(db);
    final double fwd = FORWARD_CURVE.getForward(T);

    for (int i = 0; i < xNodes; i++) {
      final double x = grid.getSpaceNode(i);
      final double k = fwd * x;
      if (k > 0.5 * fwd && k < 2.0 * fwd) {
        final double modifiedPrice = res.getFunctionValue(i);
        final double vol = BlackFormulaRepository.impliedVolatility(modifiedPrice, 1.0, x, T, true);
        final double sabrVol = SABR_VOL_SURFACE.getVolatility(T, k);
        if (DEBUG) {
          System.out.println(k + "\t" + vol + "\t" + sabrVol);
        } else {
          assertEquals("testForwardPDE vols " + k, sabrVol, vol, 8e-4); //8bps error
        }
      }
    }

  }

  /**
   * Run a classic forwards PDE once, using the local volatility derived from the
   * SABR implied volatility surface, and check the prices at the example expiry and strikes agrees with SABR.
   */
  @Test
  public void testClassicForwardsPDE() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;
    final double fwd = FORWARD_CURVE.getForward(T);
    final double maxStrike = 4.0 * fwd;

    // final ZZConvectionDiffusionPDEDataBundle pdeData = PDE_DATA_PROVIDER.getForwardLocalVol(RATE, RATE - DRIFT, SPOT, true, SABR_LOCAL_VOL);
    final ConvectionDiffusionPDE1DCoefficients pde = PDE_PROVIDER.getForwardLocalVolatility(RATE, RATE - DRIFT, SABR_LOCAL_VOL);
    final Function1D<Double, Double> initialCondition = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(SPOT, true);

    final BoundaryCondition lower = new DirichletBoundaryCondition(new Function1D<Double, Double>() {
      @Override
      public Double evaluate(final Double t) {
        return SPOT * Math.exp(-t * (RATE - DRIFT));
      }
    }, 0.0);
    final BoundaryCondition upper = new DirichletBoundaryCondition(0.0, maxStrike);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 5.0);
    final MeshingFunction spaceMesh = new HyperbolicMeshing(0.0, maxStrike, SPOT, xNodes, 0.1);
    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db = new PDE1DDataBundle<>(pde, initialCondition, lower, upper, grid);

    final PDEResults1D res = solver.solve(db);
    final double df = Math.exp(-RATE * T);
    //   System.out.println("Classic forward PDE");
    for (int i = 0; i < xNodes; i++) {
      final double k = grid.getSpaceNode(i);
      if (k > 0.5 * fwd && k < 2.0 * fwd) {
        final double price = res.getFunctionValue(i);
        final double vol = BlackFormulaRepository.impliedVolatility(price / df, fwd, k, T, true);
        final double sabrVol = SABR_VOL_SURFACE.getVolatility(T, k);
        if (DEBUG) {
          System.out.println(k + "\t" + vol + "\t" + sabrVol);
        } else {
          assertEquals("testClassicForwardsPDE. Strike = " + k, sabrVol, vol, 15e-4); //15bps error
        }
      }
    }

  }

  /**
   * For delta calculations we use the forward (or driftless) delta (pips forward delta in FX speak), which is the sensitivity of the forward option
   * price to a change in the relevant forward value of the underlying. In a Black-Scholes world this is just N(d1) (for a call) - i.e. no
   * exp(.) factors.
   * The delta under local volatility means the above sensitivity with the local volatility surface fixed, i.e. it is invariant
   * to a change of the forward curve. If the local volatility surface was allowed to change with the forward, then a different
   * delta value would be obtained. For example, we can calculated a delta for the SABR model by assuming that the SABR parameters
   * (alpha, beta, rho & nu) do not change when the forward moves - this delta will be different to what is calculated below.
   * Since there is no way of analytically calculating the delta for an arbitrary local volatility surface, we compare the results of running the
   * backwards and forwards PDE solver
   */
  @Test
  public void testLocalVolDelta() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;

    final double forward = FORWARD_CURVE.getForward(T);
    final double maxForward = 5.0 * forward;
    final double maxMoneyness = 5.0;
    final double maxStrike = 5.0 * forward;
    final double shift = 1e-3;

    final LocalVolatilitySurfaceMoneyness lvUp = LocalVolatilitySurfaceConverter.shiftForwardCurve(LV_M, shift);
    final LocalVolatilitySurfaceMoneyness lvDown = LocalVolatilitySurfaceConverter.shiftForwardCurve(LV_M, -shift);

    final ConvectionDiffusionPDE1DCoefficients pdeFwd = PDE_PROVIDER.getForwardLocalVol(LV_M);
    final ConvectionDiffusionPDE1DCoefficients pdeClasFwd = PDE_PROVIDER.getForwardLocalVolatility(RATE, RATE - DRIFT, SABR_LOCAL_VOL);
    final ConvectionDiffusionPDE1DCoefficients pdeFwdUp = PDE_PROVIDER.getForwardLocalVol(lvUp);
    final ConvectionDiffusionPDE1DCoefficients pdeFwdDown = PDE_PROVIDER.getForwardLocalVol(lvDown);

    final Function1D<Double, Double> initialCondFwd = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(true);
    final Function1D<Double, Double> initialCondClasFwdUp = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(SPOT * (1 + shift), true);
    final Function1D<Double, Double> initialCondClasFwdDown = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(SPOT * (1 - shift), true);

    final BoundaryCondition lowerFwd = new DirichletBoundaryCondition(1.0, 0.0);
    final BoundaryCondition upperFwd = new DirichletBoundaryCondition(0.0, maxMoneyness);
    final BoundaryCondition lowerBwd = new DirichletBoundaryCondition(0.0, 0.0);
    final BoundaryCondition lowerUp = new DirichletBoundaryCondition(new Function1D<Double, Double>() {
      @Override
      public Double evaluate(final Double t) {
        return SPOT * (1 + shift) * Math.exp(-t * (RATE - DRIFT));
      }
    }, 0.0);
    final BoundaryCondition lowerDown = new DirichletBoundaryCondition(new Function1D<Double, Double>() {
      @Override
      public Double evaluate(final Double t) {
        return SPOT * (1 - shift) * Math.exp(-t * (RATE - DRIFT));
      }
    }, 0.0);
    final BoundaryCondition upperCFwd = new DirichletBoundaryCondition(0.0, maxStrike);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 5.0);
    final MeshingFunction spaceMeshFwd = new HyperbolicMeshing(0.0, maxMoneyness, 1.0, xNodes, 0.1);
    final MeshingFunction spaceMeshBwd = new HyperbolicMeshing(0.0, maxForward, forward, xNodes, 0.1);
    final MeshingFunction spaceMeshCFwd = new HyperbolicMeshing(0.0, maxStrike, forward, xNodes, 0.1);
    final PDEGrid1D gridFwd = new PDEGrid1D(timeMesh, spaceMeshFwd);
    final PDEGrid1D gridCFwd = new PDEGrid1D(timeMesh, spaceMeshCFwd);
    final PDEGrid1D gridBwd = new PDEGrid1D(timeMesh, spaceMeshBwd);

    final PDEResults1D res = solver.solve(new PDE1DDataBundle<>(pdeFwd, initialCondFwd, lowerFwd, upperFwd, gridFwd));
    final PDEResults1D resUp = solver.solve(new PDE1DDataBundle<>(pdeFwdUp, initialCondFwd, lowerFwd, upperFwd, gridFwd));
    final PDEResults1D resDown = solver.solve(new PDE1DDataBundle<>(pdeFwdDown, initialCondFwd, lowerFwd, upperFwd, gridFwd));
    final PDEResults1D resCUp = solver.solve(new PDE1DDataBundle<>(pdeClasFwd, initialCondClasFwdUp, lowerUp, upperCFwd, gridCFwd));
    final PDEResults1D resCDown = solver.solve(new PDE1DDataBundle<>(pdeClasFwd, initialCondClasFwdDown, lowerDown, upperCFwd,
        gridCFwd));

    final double q = Math.exp(-(RATE - DRIFT) * T);

    for (int i = 0; i < xNodes; i++) {
      final double x = res.getSpaceValue(i);
      final double k = x * forward;
      assertEquals("strikes", k, gridCFwd.getSpaceNode(i), 1e-6);
      final double modelDD = res.getFirstSpatialDerivative(i);
      final double fixedSurfaceDelta = res.getFunctionValue(i) - x * modelDD;
      final double surfaceDelta = (resUp.getFunctionValue(i) - resDown.getFunctionValue(i)) / 2 / shift;
      final double deltaFwd = fixedSurfaceDelta + surfaceDelta;

      final double priceUp = resCUp.getFunctionValue(i);
      final double priceDown = resCDown.getFunctionValue(i);

      final double deltaCFwd = (priceUp - priceDown) / 2 / SPOT / shift / q;

      final BoundaryCondition upperBwd = new DirichletBoundaryCondition(maxForward - k, maxForward);

      final Function1D<Double, Double> payoff = INITIAL_CONDITIONS_PROVIDER.getEuropeanPayoff(k, true);
      final PDEResults1D resBkd = solver.solve(new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(PDE, payoff, lowerBwd, upperBwd, gridBwd));//pdeDataBkd, gridBwd, lowerBwd, upperBwd);

      //since the actual forward (i.e. F(0,T)) does not necessarily lie on the grid, we have to interpolate
      final double[] tFwd = new double[xNodes];
      final double[] tDelta = new double[xNodes];
      int count = 0;
      for (int j = 0; j < xNodes; j++) {
        final double fwd = gridBwd.getSpaceNode(j);
        if (fwd > 0.9 * forward && fwd < 1.1 * forward) {
          tFwd[count] = fwd;
          tDelta[count] = resBkd.getFirstSpatialDerivative(j);
          count++;
        }
      }
      final double[] fwds = new double[count];
      final double[] deltas = new double[count];
      System.arraycopy(tFwd, 0, fwds, 0, count);
      System.arraycopy(tDelta, 0, deltas, 0, count);
      final Interpolator1DDoubleQuadraticDataBundle db = INTERPOLATOR_1D.getDataBundle(fwds, deltas);
      final double deltaBkd = INTERPOLATOR_1D.interpolate(db, forward);

      if (DEBUG) {
        System.out.println(k + "\t" + deltaFwd + "\t" + deltaCFwd + "\t" + deltaBkd);
      } else {
        assertEquals("testLocalVolDelta", deltaFwd, deltaBkd, 2e-2);
        assertEquals("testLocalVolDelta2", deltaFwd, deltaCFwd, 2e-2); //TODO this is not as accurate as one would like
      }
    }
  }

  @Test
  public void testLocalVolGamma() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;

    final double forward = FORWARD_CURVE.getForward(T);
    final double maxForward = 5.0 * forward;
    final double maxMoneyness = 5.0;
    final double shift = 1e-3;

    final LocalVolatilitySurfaceMoneyness lvUp = LocalVolatilitySurfaceConverter.shiftForwardCurve(LV_M, shift);
    final LocalVolatilitySurfaceMoneyness lvDown = LocalVolatilitySurfaceConverter.shiftForwardCurve(LV_M, -shift);

    //    final ZZConvectionDiffusionPDEDataBundle pdeDataFwd = PDE_DATA_PROVIDER.getForwardLocalVol(LV_M, true);
    //    final ZZConvectionDiffusionPDEDataBundle pdeDataUp = PDE_DATA_PROVIDER.getForwardLocalVol(lvUp, true);
    //    final ZZConvectionDiffusionPDEDataBundle pdeDataDown = PDE_DATA_PROVIDER.getForwardLocalVol(lvDown, true);

    final ConvectionDiffusionPDE1DCoefficients pdeFwd = PDE_PROVIDER.getForwardLocalVol(LV_M);
    final ConvectionDiffusionPDE1DCoefficients pdeFwdUp = PDE_PROVIDER.getForwardLocalVol(lvUp);
    final ConvectionDiffusionPDE1DCoefficients pdeFwdDown = PDE_PROVIDER.getForwardLocalVol(lvDown);

    final Function1D<Double, Double> initialCondFwd = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(true);

    final BoundaryCondition lowerFwd = new DirichletBoundaryCondition(1.0, 0.0);
    final BoundaryCondition upperFwd = new DirichletBoundaryCondition(0.0, maxMoneyness);
    final BoundaryCondition lowerBwd = new DirichletBoundaryCondition(0, 0);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 5.0);
    final MeshingFunction spaceMeshFwd = new HyperbolicMeshing(0.0, maxMoneyness, 1.0, xNodes, 0.1);
    final MeshingFunction spaceMeshBwd = new HyperbolicMeshing(0.0, maxForward, forward, xNodes, 0.1);
    final PDEGrid1D gridFwd = new PDEGrid1D(timeMesh, spaceMeshFwd);
    final PDEGrid1D gridBwd = new PDEGrid1D(timeMesh, spaceMeshBwd);

    final PDEResults1D res = solver.solve(new PDE1DDataBundle<>(pdeFwd, initialCondFwd, lowerFwd, upperFwd, gridFwd));
    final PDEResults1D resUp = solver.solve(new PDE1DDataBundle<>(pdeFwdUp, initialCondFwd, lowerFwd, upperFwd, gridFwd));
    final PDEResults1D resDown = solver.solve(new PDE1DDataBundle<>(pdeFwdDown, initialCondFwd, lowerFwd, upperFwd, gridFwd));

    for (int i = 0; i < xNodes; i++) {
      final double x = res.getSpaceValue(i);
      final double k = x * forward;

      final double modelDD = res.getFirstSpatialDerivative(i);
      final double fixedSurfaceDelta = res.getFunctionValue(i) - x * modelDD;
      final double surfaceDelta = (resUp.getFunctionValue(i) - resDown.getFunctionValue(i)) / 2 / shift / forward;
      final double deltaFwd = fixedSurfaceDelta + forward * surfaceDelta;
      final double fixedSurfaceGamma = x * x / forward * res.getSecondSpatialDerivative(i);
      final double surfaceVanna = (resUp.getFirstSpatialDerivative(i) - resDown.getFirstSpatialDerivative(i)) / 2 / forward / shift;
      final double surfaceGamma = (resUp.getFunctionValue(i) + resDown.getFunctionValue(i) - 2 * res.getFunctionValue(i)) / forward / shift / shift;
      final double gammaFwd = fixedSurfaceGamma + 2 * surfaceDelta - 2 * x * surfaceVanna + surfaceGamma;

      final BoundaryCondition upperBwd = new DirichletBoundaryCondition(maxForward - k, maxForward);
      // final ZZConvectionDiffusionPDEDataBundle pdeDataBkd = PDE_DATA_PROVIDER.getBackwardsLocalVol(k, T, true, SABR_LOCAL_VOL, FORWARD_CURVE);
      final Function1D<Double, Double> payoff = INITIAL_CONDITIONS_PROVIDER.getEuropeanPayoff(k, true);
      final PDEResults1D resBkd = solver.solve(new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(PDE, payoff, lowerBwd, upperBwd, gridBwd));

      //since the actual forward (i.e. F(0,T)) does not necessarily lie on the grid, we have to interpolate
      final double[] tFwd = new double[xNodes];
      final double[] tDelta = new double[xNodes];
      final double[] tGamma = new double[xNodes];
      int count = 0;
      for (int j = 0; j < xNodes; j++) {
        final double fwd = gridBwd.getSpaceNode(j);
        if (fwd > 0.9 * forward && fwd < 1.1 * forward) {
          tFwd[count] = fwd;
          tDelta[count] = resBkd.getFirstSpatialDerivative(j);
          tGamma[count] = resBkd.getSecondSpatialDerivative(j);
          count++;
        }
      }
      final double[] fwds = new double[count];
      final double[] deltas = new double[count];
      final double[] gammas = new double[count];
      System.arraycopy(tFwd, 0, fwds, 0, count);
      System.arraycopy(tDelta, 0, deltas, 0, count);
      System.arraycopy(tGamma, 0, gammas, 0, count);
      final Interpolator1DDoubleQuadraticDataBundle dbDelta = INTERPOLATOR_1D.getDataBundle(fwds, deltas);
      final Interpolator1DDoubleQuadraticDataBundle dbGamma = INTERPOLATOR_1D.getDataBundle(fwds, gammas);
      final double deltaBkd = INTERPOLATOR_1D.interpolate(dbDelta, forward);
      final double gammaBkd = INTERPOLATOR_1D.interpolate(dbGamma, forward);

      if (DEBUG) {
        System.out.println(k + "\t" + deltaFwd + "\t" + deltaBkd + "\t" + gammaFwd + "\t" + gammaBkd + "\t" + fixedSurfaceGamma + "\t" + surfaceDelta + "\t"
            + surfaceVanna + "\t" + surfaceGamma);
      } else {
        assertEquals("testLocalVolDelta", gammaFwd, gammaBkd, 5e-1);//TODO still a large error here
      }
    }
  }

  /**
   * When the local volatility surface parametrised by moneyness (rather than strike) is made invariant to changes in the forward curve, the resulting
   * delta is that of a volatility model which is a function of moneyness only. An example of this is SABR with beta = 1.
   */
  @Test
  public void testSABRDelta() {
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
    final int tNodes = 50;
    final int xNodes = 101;

    final double forward = FORWARD_CURVE.getForward(T);
    final double maxMoneyness = 5.0;
    final SABRFormulaData sabrData = new SABRFormulaData(ATM_VOL.evaluate(T), 1.0, RHO, NU.evaluate(T));
    final LocalVolatilitySurfaceStrike locVol = getSABRLocalVolSurface(1.0, FORWARD_CURVE);

    // final ZZConvectionDiffusionPDEDataBundle pdeDataFwd = PDE_DATA_PROVIDER.getForwardLocalVol(locVol, FORWARD_CURVE, true);
    final ConvectionDiffusionPDE1DCoefficients pde = PDE_PROVIDER.getForwardLocalVol(FORWARD_CURVE, locVol);
    final Function1D<Double, Double> initialCond = INITIAL_CONDITIONS_PROVIDER.getForwardCallPut(true);

    final BoundaryCondition lowerFwd = new DirichletBoundaryCondition(1.0, 0.0);
    final BoundaryCondition upperFwd = new DirichletBoundaryCondition(0.0, maxMoneyness);

    final MeshingFunction timeMesh = new ExponentialMeshing(0.0, T, tNodes, 5.0);
    final MeshingFunction spaceMeshFwd = new HyperbolicMeshing(0.0, maxMoneyness, 1.0, xNodes, 0.1);

    final PDEGrid1D gridFwd = new PDEGrid1D(timeMesh, spaceMeshFwd);
    final PDEResults1D res = solver.solve(new PDE1DDataBundle<>(pde, initialCond, lowerFwd, upperFwd, gridFwd));

    for (int i = 1; i < xNodes - 1; i++) {
      final double x = res.getSpaceValue(i);
      final double k = x * forward;
      final double modelDD = res.getFirstSpatialDerivative(i);
      final double fixedSurfaceDelta = res.getFunctionValue(i) - x * modelDD;
      final double[] volAdjoint = SABR.getVolatilityAdjoint(new EuropeanVanillaOption(k, T, true), forward, sabrData);
      final double bsDelta = BlackFormulaRepository.delta(forward, k, T, volAdjoint[0], true);
      final double bsVega = BlackFormulaRepository.vega(forward, k, T, volAdjoint[0]);
      final double sabrDelta = bsDelta + bsVega * volAdjoint[1];

      if (DEBUG) {
        System.out.println(k + "\t" + fixedSurfaceDelta + "\t" + sabrDelta);
      } else {
        assertEquals("testSABRDelta", sabrDelta, fixedSurfaceDelta, 1e-2);

      }
    }
  }

  private static BlackVolatilitySurfaceStrike getSABRImpliedVolSurface(final double beta, final ForwardCurve forwardCurve) {

    final Function<Double, Double> sabrSurface = new Function<Double, Double>() {
      @SuppressWarnings("synthetic-access")
      @Override
      public Double evaluate(final Double... x) {
        final double t = x[0];
        final double k = x[1];
        final double fwd = forwardCurve.getForward(t);
        final double alpha = ATM_VOL.evaluate(T) * Math.pow(fwd, 1 - beta);
        final double nu = NU.evaluate(t);
        final SABRFormulaData data = new SABRFormulaData(alpha, beta, RHO, nu);
        final EuropeanVanillaOption option = new EuropeanVanillaOption(k, t, true);
        return SABR.getVolatility(option, fwd, data);
      }
    };

    return new BlackVolatilitySurfaceStrike(FunctionalDoublesSurface.from(sabrSurface));
  }

  private static LocalVolatilitySurfaceStrike getSABRLocalVolSurface(final double beta, final ForwardCurve forwardCurve) {
    final DupireLocalVolatilityCalculator cal = new DupireLocalVolatilityCalculator();
    final BlackVolatilitySurfaceStrike impVol = getSABRImpliedVolSurface(beta, forwardCurve);
    return cal.getLocalVolatility(impVol, forwardCurve);
  }

  private static PriceSurface getSABRPriceSurface(final double beta, final ForwardCurve forwardCurve, final YieldAndDiscountCurve discountCurve) {

    final BlackVolatilitySurfaceStrike impVol = getSABRImpliedVolSurface(beta, forwardCurve);
    final Function<Double, Double> priceSurface = new Function<Double, Double>() {
      @Override
      public Double evaluate(final Double... x) {
        final double t = x[0];
        final double k = x[1];
        final double fwd = forwardCurve.getForward(t);
        final double vol = impVol.getVolatility(t, k);
        return discountCurve.getDiscountFactor(t) * BlackFormulaRepository.price(fwd, k, t, vol, true);
      }
    };
    return new PriceSurface(FunctionalDoublesSurface.from(priceSurface));
  }

}
TOP

Related Classes of com.opengamma.analytics.financial.model.finitedifference.SABRFiniteDifferenceTest

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.