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

Source Code of com.opengamma.analytics.financial.model.finitedifference.applications.BlackScholesMertonPDEPricerTest

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

import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;

import org.testng.annotations.Test;

import com.opengamma.analytics.financial.model.option.pricing.analytic.BjerksundStenslandModel;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.util.test.TestGroup;

/**
*
*/
@Test(groups = TestGroup.UNIT_SLOW)
public class BlackScholesMertonPDEPricerTest {
  private static final BjerksundStenslandModel AMERICAN_APPOX_PRCIER = new BjerksundStenslandModel();
  private static final BlackScholesMertonPDEPricer PRICER = new BlackScholesMertonPDEPricer();

  /**
   * This tests that the run time scales as the grid size for moderately sized grids. The grid size is the total number of nodes in the grid (#space nodes x #time nodes)
   * and moderately sized means between 1e5 and 1e9 nodes (e.g. from 100 time nodes and 1000 space nodes to 1e4 time and 1e5 space nodes).
   * We also test that the relative error is inversely proportional to the grid size; the constant of proportionality depends on nu (the ration of space nodes to time nodes) and
   * is lowest for nu = 125 in this case.
   */
  @Test
  public void speedAccuracyTest() {
    final double s0 = 10.0;
    final double k = 13.0;
    final double r = 0.06;
    final double b = 0.04;
    final double t = 1.75;
    final double sigma = 0.5;
    final boolean isCall = true;

    // warm-up
    double nu = 125;
    int tSteps = 100;
    int sSteps = (int) (nu * tSteps);
    double pdePDE = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);
    nu = 125;

    //   double scale = 0;
    for (int i = 0; i < 5; i++) {
      tSteps = (int) (100 + Math.pow(10.0, (i + 6.0) / 4.0));
      sSteps = (int) (nu * tSteps);
      final double size = tSteps * sSteps;

      final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
      //      double startTime = System.nanoTime();
      pdePDE = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);
      //      double endTime = System.nanoTime();
      //     double duration = endTime - startTime; // in ns

      final double relErr = Math.abs(1 - pdePDE / bsPrice);
      // System.out.println(tSteps + "\t" + sSteps + "\t" + duration + "\t" + ((double) tSteps) * sSteps + "\t" + relErr);

      //Removed because fails on bamboo
      // check correct scaling of run time with grid size
      //      if (i == 0) {
      //        scale = duration / size; // time per node (ns/node)
      //      } else {
      //        assertTrue("runtime not scaling linearly with grid size\t" + duration / size + "\t" + scale, Math.abs(duration / size - scale) < 0.4 * scale);
      //      }

      // check correct scaling of error with grid size
      final double logErrorCeil = Math.log10(1.25 / size);
      final double logRelError = Math.log10(relErr);
      assertTrue("error exceeds ceiling " + logRelError + "\t" + logErrorCeil, logRelError < logErrorCeil);
      assertEquals("error smaller than expected", logErrorCeil, logRelError, 1e-1);
    }
  }

  /**
   * Test that a wide range of European options price to reasonable accuracy on a moderately sized grid
   */
  @Test
  public void europeanTest() {
    final double s0 = 10.0;
    final double[] kSet = {7.0, 9.0, 10.0, 13.0, 17.0 };
    final double[] rSet = {0.0, 0.04, 0.2 };
    final double[] qSet = {-0.05, 0.0, 0.1 };
    final double[] tSet = {0.1, 2.0 };
    final double sigma = 0.3;
    final boolean[] isCallSet = {true, false };

    final int tSteps = 100;
    final int nu = 80;
    final int sSteps = nu * tSteps;

    for (final double k : kSet) {
      for (final double r : rSet) {
        for (final double q : qSet) {
          final double b = r - q;
          for (final double t : tSet) {
            for (final boolean isCall : isCallSet) {
              final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
              final double pdePDE = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);
              final double absErr = Math.abs(pdePDE - bsPrice);
              final double relErr = Math.abs(1 - pdePDE / bsPrice);
              // System.out.println(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + bsPrice + "\t" + pdePDE + "\t" + absErr + "\t" + relErr);
              if (k < 17.0) {
                assertTrue(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + bsPrice + "\t" + pdePDE + "\t" + absErr + "\t" + relErr, absErr < 3e-5);
              } else {
                assertTrue(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + bsPrice + "\t" + pdePDE + "\t" + absErr + "\t" + relErr, absErr < 1e-4);
              }
            }
          }
        }
      }
    }
  }

  /**
   * Test that a wide range of American options price to within the accuracy of the Bjerksund-Stensland approximation on a moderately sized grid
   */
  @Test
  public void americanTest() {
    final double s0 = 10.0;
    final double[] kSet = {7.0, 9.0, 10.0, 13.0, 17.0 };
    final double[] rSet = {0.0, 0.04, 0.2 };
    final double[] qSet = {-0.05, 0.0, 0.1 };
    final double[] tSet = {0.05, 0.25 };
    final double sigma = 0.3;
    final boolean[] isCallSet = {true, false };

    // The Bjerksund-Stensland approximation is not that accurate, so there is no point using a fine grid for this test
    final int tSteps = 80;
    final int nu = 20;
    final int sSteps = nu * tSteps;

    for (final double k : kSet) {
      for (final double r : rSet) {
        for (final double q : qSet) {
          final double b = r - q;
          for (final double t : tSet) {
            for (final boolean isCall : isCallSet) {
              final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
              final double amAprox = AMERICAN_APPOX_PRCIER.price(s0, k, r, b, t, sigma, isCall);
              final double pdePrice = PRICER.price(s0, k, r, b, t, sigma, isCall, true, sSteps, tSteps);

              // Bjerksund-Stensland approximation set a lower limit for the price of an American option, thus the PDE price should always exceed it
              assertTrue(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + bsPrice + "\t" + amAprox + "\t" + pdePrice, (pdePrice - amAprox) > -5e-6);

              final double absErr = Math.abs(pdePrice - amAprox);
              final double relErr = Math.abs(1 - pdePrice / amAprox);

              // System.out.println(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + amAprox + "\t" + pdePDE + "\t" + absErr + "\t" + relErr);
              assertTrue(k + "\t" + r + "\t" + q + "\t" + t + "\t" + isCall + "\t" + amAprox + "\t" + pdePrice + "\t" + absErr + "\t" + relErr, absErr < 1e-2);
            }
          }
        }
      }
    }
  }

  @Test
  public void nonuniformGridTest() {
    final double s0 = 10.0;
    final double k = 13.0;
    final double r = 0.06;
    final double b = 0.04;
    final double t = 1.75;
    final double sigma = 0.5;
    final boolean isCall = false;

    final double beta = 0.01;
    final double lambda = 0.0;
    final double sd = 6.0;

    final int tSteps = 100;
    final double nu = 10;
    final int sSteps = (int) (nu * tSteps);

    final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
    final double pdePrice1 = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);
    final double pdePrice2 = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps, beta, lambda, sd);
    final double relErr1 = Math.abs(pdePrice1 / bsPrice - 1.0);
    final double relErr2 = Math.abs(pdePrice2 / bsPrice - 1.0);
    //System.out.println(tSteps + "\t" + sSteps + "\t" + ((double) tSteps) * sSteps + "\t" + bsPrice + "\t" + pdePrice1 + "\t" + pdePrice2 + "\t" + relErr1 + "\t" + relErr2);

    assertEquals(0, relErr1, 5e-4);
    assertEquals(0, relErr2, 2e-6); // much better accuracy with non-uniform
  }

  @Test(enabled = false)
  public void optNuTest() {
    final double s0 = 10.0;
    final double k = 13.0;
    final double r = 0.06;
    final double b = 0.04;
    final double t = 1.75;
    final double sigma = 0.5;
    final boolean isCall = true;

    // warm-up
    final double nu = 80;
    int tSteps = 100;
    int sSteps = (int) (nu * tSteps);
    double pdePDE = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);

    final double[] nuSet = new double[] {1, 2, 5, 10, 20, 40, 60, 80, 100, 125, 150, 200, 500 };
    final int n = (int) 1e9;
    for (final double nu1 : nuSet) {
      tSteps = (int) Math.sqrt(n / nu1);
      sSteps = n / tSteps;
      final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
      final double startTime = System.nanoTime() / 1e6;
      pdePDE = PRICER.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);
      final double endTime = System.nanoTime() / 1e6;
      final double duration = endTime - startTime;

      final double relErr = Math.abs(1 - pdePDE / bsPrice);
      System.out.println(tSteps + "\t" + sSteps + "\t" + duration + "\t" + nu1 + "\t" + relErr);
    }

  }

  @Test(enabled = false)
  public void unifromGridAmericanCallTest() {
    final double s0 = 90;
    final double k = 100;
    final double r = -1e-5;
    final double b = 0.04;
    final double sigma = 0.35;
    final double t = 0.5;
    final boolean isCall = false;
    // final double r = 1e-6 + b - 0.5 * (2 * b + sigma * sigma) * (2 * b + sigma * sigma) / 4 / sigma / sigma;
    System.out.println(r + "\t" + b + "\t" + -0.5 * sigma * sigma * (b / sigma / sigma - 0.5) * (b / sigma / sigma - 0.5));

    final int tSteps = 100;
    final int nu = 80;
    final int sSteps = nu * tSteps;

    // for (int i = 0; i < 50; i++) {
    // r = -0.08 + 0.1 * i / 49.0;
    // b = -0.02 + 0.12 * i / 49.0;
    // b = 0.061;

    final double bsPrice = Math.exp(-r * t) * BlackFormulaRepository.price(s0 * Math.exp(b * t), k, t, sigma, isCall);
    final double bsPrice2 = Math.exp(-(r - b) * t) * BlackFormulaRepository.price(k * Math.exp(-b * t), s0, t, sigma, !isCall);
    final double amAprox = AMERICAN_APPOX_PRCIER.price(s0, k, r, b, t, sigma, isCall);
    final double pdePrice = PRICER.price(s0, k, r, b, t, sigma, isCall, true, sSteps, tSteps);
    final double pdePricePC = 0.0; // PRICER.price(k, s0, r - b, -b, t, sigma, !isCall, true, sSteps, tSteps);

    System.out.println(b + "\t" + bsPrice + "\t" + bsPrice2 + "\t" + amAprox + "\t" + pdePrice + "\t" + pdePricePC + "\t" + (1 - pdePrice / bsPrice));
  }

  @Test(enabled = false)
  public void debugTest() {
    System.out.println("BlackScholesMertonPDEPricerTest.debugTest");
    final BjerksundStenslandModel amPricer = new BjerksundStenslandModel();
    final BlackScholesMertonPDEPricer pricer = new BlackScholesMertonPDEPricer(false);
    final double s0 = 10.0;
    final double k = 7.0;
    final double r = 0.2;
    final double b = 0.1;
    final double t = 2.0;
    final double sigma = 0.3;
    final boolean isCall = false;
    final boolean isAmerican = true;

    final double nu = 80;
    final int tSteps = 500;
    final int sSteps = (int) (nu * tSteps);
    // warm-up
    pricer.price(s0, k, r, b, t, sigma, isCall, false, sSteps, tSteps);

    final double df = Math.exp(-r * t);
    final double fwd = s0 * Math.exp(b * t);
    final double bsPrice = df * BlackFormulaRepository.price(fwd, k, t, sigma, isCall);
    final double startTime = System.nanoTime() / 1e6;
    final double pdePrice = pricer.price(s0, k, r, b, t, sigma, isCall, isAmerican, sSteps, tSteps);
    final double endTime = System.nanoTime() / 1e6;
    final double duration = endTime - startTime;


    final double amPrice = amPricer.price(s0, k, r, b, t, sigma, isCall);
    final double relErr = Math.abs(1 - pdePrice / amPrice);
    System.out.println(tSteps + "\t" + sSteps + "\t" + duration + df * fwd + "\t" + amPrice + "\t" + pdePrice + "\t" + relErr);

  }
}
// }
TOP

Related Classes of com.opengamma.analytics.financial.model.finitedifference.applications.BlackScholesMertonPDEPricerTest

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.