Example #1
0
 static {
   FORWARD_CURVE = new ForwardCurve(SPOT, DRIFT);
   LOCAL_VOL =
       new LocalVolatilitySurfaceMoneyness(ConstantDoublesSurface.from(FLAT_VOL), FORWARD_CURVE);
   PDE = PDE_DATA_PROVIDER.getLogBackwardsLocalVol(EXPIRY, LOCAL_VOL);
   INITIAL_COND = INT_COND_PROVIDER.getLogContractPayoffInLogCoordinate();
 }
  /**
   * Prices a log-contract for a given local volatility surface by backwards solving the PDE
   * expressed in terms of (the log of) the real stock price - this requires having jumps conditions
   * in the PDE
   *
   * @param lv Local volatility
   * @return Forward (non-discounted) price of log-contact
   */
  private double logContractPriceFromSpotPDE(final LocalVolatilitySurfaceStrike lv) {

    // Set up the PDE to give the forward (non-discounted) option price
    final ConvectionDiffusionPDE1DCoefficients pde =
        PDE_PROVIDER.getLogBackwardsLocalVol(0.0, -DRIFT, EXPIRY, lv);
    final Function1D<Double, Double> initialCon =
        INITIAL_COND_PROVIDER.getLogContractPayoffInLogCoordinate();

    final double theta = 0.5;
    final double yL = Math.log(SPOT / 6);
    final double yH = Math.log(6 * SPOT);
    final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(theta, false);

    final BoundaryCondition lower1 = new NeumannBoundaryCondition(1.0, yL, true);
    final BoundaryCondition upper1 = new NeumannBoundaryCondition(1.0, yH, false);

    final MeshingFunction timeMesh1 =
        new ExponentialMeshing(0, EXPIRY - DIVIDEND_DATE - 1e-6, 50, 0.0);
    final MeshingFunction timeMesh2 =
        new ExponentialMeshing(EXPIRY - DIVIDEND_DATE + 1e-6, EXPIRY, 50, 0.0);
    final MeshingFunction spaceMesh = new ExponentialMeshing(yL, yH, 101, 0.0);

    final PDEGrid1D grid1 = new PDEGrid1D(timeMesh1, spaceMesh);
    final double[] sNodes1 = grid1.getSpaceNodes();

    // run the PDE solver backward to the dividend date
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db1 =
        new PDE1DDataBundle<>(pde, initialCon, lower1, upper1, grid1);
    final PDETerminalResults1D res1 = (PDETerminalResults1D) solver.solve(db1);

    // Map the spot nodes after (in calendar time) the dividend payment to nodes before
    final int nSNodes = sNodes1.length;
    final double[] sNodes2 = new double[nSNodes];
    final double lnBeta = Math.log(1 - BETA);
    for (int i = 0; i < nSNodes; i++) {
      final double temp = sNodes1[i];
      if (temp < 0) {
        sNodes2[i] = Math.log(Math.exp(temp) + ALPHA) - lnBeta;
      } else {
        sNodes2[i] = temp + Math.log(1 + ALPHA * Math.exp(-temp)) - lnBeta;
      }
    }

    final PDEGrid1D grid2 = new PDEGrid1D(timeMesh2.getPoints(), sNodes2);
    final BoundaryCondition lower2 = new NeumannBoundaryCondition(1.0, sNodes2[0], true);
    final BoundaryCondition upper2 = new NeumannBoundaryCondition(1.0, sNodes2[nSNodes - 1], false);

    // run the PDE solver backward from the dividend date to zero
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> db2 =
        new PDE1DDataBundle<>(pde, res1.getTerminalResults(), lower2, upper2, grid2);
    final PDETerminalResults1D res2 = (PDETerminalResults1D) solver.solve(db2);

    final Interpolator1DDataBundle interpolDB2 =
        INTEPOLATOR1D.getDataBundle(sNodes2, res2.getTerminalResults());
    final double val2 = INTEPOLATOR1D.interpolate(interpolDB2, Math.log(SPOT));
    return val2;
  }
  protected double blackPrice(
      final double spot,
      final double strike,
      final double expiry,
      final double rate,
      final double carry,
      final double vol,
      final boolean isCall) {

    final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
    final ConvectionDiffusionPDE1DStandardCoefficients pde =
        PDE.getBlackScholes(rate, rate - carry, vol);

    double sMin = 0.0;
    double sMax = spot * Math.exp(_z * Math.sqrt(expiry));
    BoundaryCondition lower;
    BoundaryCondition upper;
    if (isCall) {
      lower = new DirichletBoundaryCondition(0.0, sMin);
      Function1D<Double, Double> upperValue =
          new Function1D<Double, Double>() {
            @Override
            public Double evaluate(Double tau) {
              return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
            }
          };
      upper = new DirichletBoundaryCondition(upperValue, sMax);
    } else {
      Function1D<Double, Double> lowerValue =
          new Function1D<Double, Double>() {
            @Override
            public Double evaluate(Double tau) {
              return Math.exp(-rate * tau) * strike;
            }
          };
      lower = new DirichletBoundaryCondition(lowerValue, sMin);
      upper = new DirichletBoundaryCondition(0.0, sMax);
    }

    final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
    final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
    final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
    PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData =
        new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, intCon, lower, upper, grid);

    PDEResults1D res = SOLVER.solve(pdeData);
    // for now just do linear interpolation on price. TODO replace this with something more robust
    final double[] xNodes = grid.getSpaceNodes();
    final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
    final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
    return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
  }
  /**
   * Computes the price of a one-touch out barrier option in the Black-Scholes world by solving the
   * BS PDE on a finite difference grid. If a barrier is hit at any time before expiry, the option
   * is cancelled (knocked-out) and a rebate (which is often zero) is paid <b>immediately</b>. If
   * the barrier is not hit, then a normal European option payment is made.
   *
   * <p>If the barrier is above the spot it is assumed to be an up-and-out barrier (otherwise it
   * would expire immediately) otherwise it is a down-and-out barrier As there are exact formulae
   * for this case (see {@link BlackBarrierPriceFunction}), this is purely for testing purposes.
   *
   * @param spot The current (i.e. option price time) of the underlying
   * @param barrierLevel The barrier level
   * @param strike The strike of the European option
   * @param expiry The expiry of the option
   * @param rate The interest rate.
   * @param carry The cost-of-carry (i.e. the forward = spot*exp(costOfCarry*T) )
   * @param vol The Black volatility.
   * @param isCall true for call
   * @param rebate The rebate amount.
   * @return The price.
   */
  public double outBarrier(
      final double spot,
      final double barrierLevel,
      final double strike,
      final double expiry,
      final double rate,
      final double carry,
      final double vol,
      final boolean isCall,
      final double rebate) {

    final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
    final ConvectionDiffusionPDE1DStandardCoefficients pde =
        PDE.getBlackScholes(rate, rate - carry, vol);
    final boolean isUp = barrierLevel > spot;

    final double adj = 0.0; // _lambda == 0 ? ZETA * vol * Math.sqrt(expiry / (_nTNodes - 1)) : 0.0;
    // System.out.println("adjustment: " + adj);

    double sMin;
    double sMax;
    BoundaryCondition lower;
    BoundaryCondition upper;
    if (isUp) {
      sMin = 0.0;
      sMax =
          barrierLevel
              * Math.exp(-adj); // bring the barrier DOWN slightly to adjust for discrete monitoring
      if (isCall) {
        lower = new DirichletBoundaryCondition(0.0, sMin);
      } else {
        Function1D<Double, Double> lowerValue =
            new Function1D<Double, Double>() {
              @Override
              public Double evaluate(Double tau) {
                return Math.exp(-rate * tau) * strike;
              }
            };
        lower = new DirichletBoundaryCondition(lowerValue, sMin);
      }
      upper = new DirichletBoundaryCondition(rebate, sMax);
    } else {
      sMin =
          barrierLevel
              * Math.exp(adj); // bring the barrier UP slightly to adjust for discrete monitoring
      sMax = spot * Math.exp(_z * Math.sqrt(expiry));
      lower = new DirichletBoundaryCondition(rebate, sMin);

      if (isCall) {
        Function1D<Double, Double> upperValue =
            new Function1D<Double, Double>() {
              @Override
              public Double evaluate(Double tau) {
                return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
              }
            };
        upper = new DirichletBoundaryCondition(upperValue, sMax);
      } else {
        upper = new DirichletBoundaryCondition(0.0, sMax);
      }
    }

    final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
    final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
    final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
    PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData =
        new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, intCon, lower, upper, grid);

    PDEResults1D res = SOLVER.solve(pdeData);
    // for now just do linear interpolation on price. TODO replace this with something more robust
    final double[] xNodes = grid.getSpaceNodes();
    final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
    final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
    return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
  }