/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.option.pricing.analytic;
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.tree.AmericanVanillaOptionFunctionProvider;
import com.opengamma.analytics.financial.model.option.pricing.tree.BinomialTreeOptionPricingModel;
import com.opengamma.analytics.financial.model.option.pricing.tree.CashDividendFunctionProvider;
import com.opengamma.analytics.financial.model.option.pricing.tree.DividendFunctionProvider;
import com.opengamma.analytics.financial.model.option.pricing.tree.LatticeSpecification;
import com.opengamma.analytics.financial.model.option.pricing.tree.LeisenReimerLatticeSpecification;
import com.opengamma.analytics.financial.model.option.pricing.tree.OptionFunctionProvider1D;
import com.opengamma.analytics.financial.model.volatility.BlackScholesFormulaRepository;
import com.opengamma.util.test.TestGroup;
/**
*
*/
@Test(groups = TestGroup.UNIT)
public class RollGeskeWhaleyModelTest {
private static final double TIME_TO_EXPIRY = 1.;
private static final double SPOT = 100.;
private static final double[] STRIKES_INPUT = new double[] {35., 89.0, 100.0, 106.0, 165.0 };
private static final double[] INTEREST_RATES = new double[] {-0.01, -0.005, 0, 0.01, 0.1, 0.2 };
private static final double[] VOLS = new double[] {0.1, 0.3, 0.8 };
private static final double[] DIVIDENDS = new double[] {15., 25. };
private static final double[] DIVIDEND_TIMES = new double[] {0.1, 0.5, 0.9 };
private static final double[][] MULTI_DIVIDENDS = new double[][] { {5., 5., 5., 5. }, {5., 8., 11., 12. } };
private static final double[][] MULTI_DIVIDEND_TIMES = new double[][] { {0.1, 0.35, 0.6, 0.85 }, {0.4, 0.7, 0.95, 1.2 } };
private static final double DELTA = 1.e-4;
private static final RollGeskeWhaleyModel MODEL = new RollGeskeWhaleyModel();
/**
* The limited case where the price reduces into Black-Scholes price
*/
@Test
public void priceLimtedTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final double eps = 1.e-5;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
/*
* For the stability of the root-finder for sStar we shall take the limit in dividendTime as well.
*/
final double dividendTimes = TIME_TO_EXPIRY - eps;
final double dividend = eps;
// System.out.println(i + "\t" + j + "\t" + k);
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], dividend, dividendTimes);
final double priceZeroDiv = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], 0., dividendTimes);
final double priceZeroTime = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], DIVIDENDS[0], 0.);
final double priceSmallTime = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], DIVIDENDS[0], eps);
final double bs = BlackScholesFormulaRepository.price(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double bsMod = BlackScholesFormulaRepository.price(SPOT - dividend, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
assertEquals(bsMod, price, Math.max(eps, Math.abs(bs) * eps));
assertEquals(bs, priceZeroDiv, 1.e-14);
assertEquals(priceSmallTime, priceZeroTime, Math.max(eps, Math.abs(priceZeroTime) * eps));
}
}
}
}
/**
*
*/
@Test
public void noDividendTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final double eps = 1.e-5;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
/*
* For the stability of the root-finder for sStar we shall take the limit in dividendTime as well.
*/
final double dividendTimes = TIME_TO_EXPIRY + eps;
final double dividend = 0.1 * SPOT;
// System.out.println(i + "\t" + j + "\t" + k);
final double[] greeks = MODEL.getPriceAdjoint(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], new double[] {dividend }, new double[] {dividendTimes });
final double price = BlackScholesFormulaRepository.price(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double delta = BlackScholesFormulaRepository.delta(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double dualDelta = BlackScholesFormulaRepository.dualDelta(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double theta = BlackScholesFormulaRepository.theta(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double rho = BlackScholesFormulaRepository.rho(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k], true);
final double vega = BlackScholesFormulaRepository.vega(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k]);
final double gamma = BlackScholesFormulaRepository.gamma(SPOT, STRIKES_INPUT[i], TIME_TO_EXPIRY, VOLS[j], INTEREST_RATES[k], INTEREST_RATES[k]);
assertEquals(price, greeks[0], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(delta, greeks[1], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(dualDelta, greeks[2], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(rho, greeks[3], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(-theta, greeks[4], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(0., greeks[5]);
assertEquals(vega, greeks[6], Math.max(eps, Math.abs(price) * 1.e-14));
assertEquals(gamma, greeks[7], Math.max(eps, Math.abs(price) * 1.e-14));
}
}
}
}
/**
* The limited case where the price reduces into modified Bjerksund-Stensland price
*/
@Test
public void priceBjsTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
final double dividend = SPOT * 1.e-3;
final double dividendTime = TIME_TO_EXPIRY * 1.e-3;
// if (dividend > (1.1 - Math.exp(-INTEREST_RATES[k] * (TIME_TO_EXPIRY - dividendTime))) * STRIKES_INPUT[i]) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + m);
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], dividend, dividendTime);
final double modSpot = SPOT - dividend * Math.exp(-INTEREST_RATES[k] * dividendTime);
final BjerksundStenslandModel bjs = new BjerksundStenslandModel();
final double bjsPrice = bjs.price(modSpot, STRIKES_INPUT[i], INTEREST_RATES[k], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], true);
/*
* Smoothing out discrete dividends sometimes produce a value which is largely different from the RGW model.
*/
// final double fwd = SPOT * Math.exp(INTEREST_RATES[k] * TIME_TO_EXPIRY) - dividend * Math.exp(INTEREST_RATES[k] * (TIME_TO_EXPIRY - dividendTime));
// final double coc = Math.log(fwd / SPOT) / TIME_TO_EXPIRY;
// final double bjsPriceFwd = bjs.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], coc, TIME_TO_EXPIRY, VOLS[j], true);
// System.out.println(price + "\t" + bjsPriceFwd + "\t" + bjsPrice);
assertEquals(bjsPrice, price, Math.max(1.e-2, Math.abs(bjsPrice) * 1.e-2));
// }
}
}
}
}
/**
* Implied volatility calculator is tested
*/
@Test
public void impliedVolTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final int nDiv = DIVIDENDS.length;
final int nDivT = DIVIDEND_TIMES.length;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
for (int l = 0; l < nDiv; ++l) {
for (int m = 0; m < nDivT; ++m) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + l + "\t" + m);
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], DIVIDENDS[l], DIVIDEND_TIMES[m]);
final double impliedVol = MODEL.impliedVolatility(price, SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, DIVIDENDS[l], DIVIDEND_TIMES[m]);
final double priceRe = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, impliedVol, DIVIDENDS[l], DIVIDEND_TIMES[m]);
/*
* Due to zero vega for small vols, as found in other American option models, comparing vol sometimes fails.
* Instead the resulting option price is tested.
*/
// assertEquals(VOLS[j], impliedVol, Math.max(DELTA, Math.abs(VOLS[j]) * DELTA));
assertEquals(price, priceRe, Math.max(price, Math.abs(price) * DELTA));
}
}
}
}
}
}
/**
* Analytic Greeks are tested
*/
@Test
public void greeksFiniteDiffTest() {
final double[] divLocal = new double[] {5., 12., 0. };
final double[] divTimeLocal = new double[] {0.1, 0.85, 0.4, 0.7, 0. };
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final int nDiv = divLocal.length;
final int nDivT = divTimeLocal.length;
final double[] upStrikes = new double[nStrikes];
final double[] dwStrikes = new double[nStrikes];
final double upSpot = SPOT * (1. + DELTA);
final double dwSpot = SPOT * (1. - DELTA);
final double upTime = TIME_TO_EXPIRY * (1. + DELTA);
final double dwTime = TIME_TO_EXPIRY * (1. - DELTA);
final double[] upVOLS = new double[nVols];
final double[] dwVOLS = new double[nVols];
final double[] upInt = new double[nInt];
final double[] dwInt = new double[nInt];
for (int i = 0; i < nStrikes; ++i) {
upStrikes[i] = STRIKES_INPUT[i] * (1. + DELTA);
dwStrikes[i] = STRIKES_INPUT[i] * (1. - DELTA);
}
for (int i = 0; i < nVols; ++i) {
upVOLS[i] = VOLS[i] * (1. + DELTA);
dwVOLS[i] = VOLS[i] * (1. - DELTA);
}
for (int i = 0; i < nInt; ++i) {
upInt[i] = INTEREST_RATES[i] == 0. ? DELTA : INTEREST_RATES[i] * (1. + DELTA);
dwInt[i] = INTEREST_RATES[i] == 0. ? -DELTA : INTEREST_RATES[i] * (1. - DELTA);
}
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
for (int l = 0; l < nDiv; ++l) {
for (int m = 0; m < nDivT; ++m) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + l + "\t" + m);
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double[] greeks = MODEL.getPriceAdjoint(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double[] greeksUp = MODEL.getPriceAdjoint(upSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double[] greeksDw = MODEL.getPriceAdjoint(dwSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceSpotUp = MODEL.price(upSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceSpotDw = MODEL.price(dwSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceStrikeUp = MODEL.price(SPOT, upStrikes[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceStrikeDw = MODEL.price(SPOT, dwStrikes[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceRateUp = MODEL.price(SPOT, STRIKES_INPUT[i], upInt[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceRateDw = MODEL.price(SPOT, STRIKES_INPUT[i], dwInt[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceTimeUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], upTime, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceTimeDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], dwTime, VOLS[j], divLocal[l], divTimeLocal[m]);
final double priceVolUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, upVOLS[j], divLocal[l], divTimeLocal[m]);
final double priceVolDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, dwVOLS[j], divLocal[l], divTimeLocal[m]);
final double deltaFin = 0.5 * (priceSpotUp - priceSpotDw) / SPOT / DELTA;
final double dualDeltaFin = 0.5 * (priceStrikeUp - priceStrikeDw) / STRIKES_INPUT[i] / DELTA;
final double rhoFin = INTEREST_RATES[k] == 0. ? 0.5 * (priceRateUp - priceRateDw) / DELTA : 0.5 * (priceRateUp - priceRateDw) / INTEREST_RATES[k] / DELTA;
final double thetaFin = 0.5 * (priceTimeUp - priceTimeDw) / TIME_TO_EXPIRY / DELTA;
final double vegaFin = 0.5 * (priceVolUp - priceVolDw) / VOLS[j] / DELTA;
final double gammaFin = 0.5 * (greeksUp[1] - greeksDw[1]) / SPOT / DELTA;
assertEquals(price, greeks[0], 1.e-12);
assertEquals(deltaFin, greeks[1], Math.max(DELTA, Math.abs(deltaFin) * DELTA));
assertEquals(dualDeltaFin, greeks[2], Math.max(DELTA, Math.abs(dualDeltaFin) * DELTA));
assertEquals(rhoFin, greeks[3], Math.max(DELTA, Math.abs(rhoFin) * DELTA));
assertEquals(thetaFin, greeks[4], Math.max(DELTA, Math.abs(thetaFin) * DELTA));
assertEquals(vegaFin, greeks[6], Math.max(DELTA, Math.abs(vegaFin) * DELTA));
assertEquals(gammaFin, greeks[7], Math.max(DELTA, Math.abs(gammaFin) * DELTA));
final boolean zeroTime = divTimeLocal[m] == 0.;
final double base = zeroTime ? 1. : divTimeLocal[m];
final double delta = zeroTime ? 1.e-7 : DELTA;
final double divTimeLocalUp = zeroTime ? delta : divTimeLocal[m] * (1. + delta);
final double divTimeLocalDw = zeroTime ? 0. : divTimeLocal[m] * (1. - delta);
final double coeff = zeroTime ? 1. : 0.5;
final double priceDivTimeUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocalUp);
final double priceDivTimeDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], divLocal[l], divTimeLocalDw);
final double thetaDivFin = coeff * (priceDivTimeUp - priceDivTimeDw) / base / delta;
assertEquals(thetaDivFin, greeks[5], Math.max(DELTA, Math.abs(thetaDivFin) * DELTA));
}
}
}
}
}
}
/**
*
*/
@Test
public void errorTest() {
final double[] params = new double[] {100., 100., 0.5, 0.2, 20., 0.8 };
final int nParams = params.length;
for (int i = 0; i < nParams; ++i) {
params[i] *= -1.;
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], params[4], params[5]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], params[4], params[5]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
params[i] *= -1.;
}
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], params[0] + params[4], params[5]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], params[0] + params[4], params[5]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], params[4], params[5] + params[2]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], params[4], params[5] + params[2]);
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
}
/**
* Analytic Greeks are tested
*/
@Test
public void greeksFiniteDiffMultiTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final int nDiv = MULTI_DIVIDENDS.length;
final int nDivT = MULTI_DIVIDEND_TIMES.length;
final double[] upStrikes = new double[nStrikes];
final double[] dwStrikes = new double[nStrikes];
final double upSpot = SPOT * (1. + DELTA);
final double dwSpot = SPOT * (1. - DELTA);
final double upTime = TIME_TO_EXPIRY * (1. + DELTA);
final double dwTime = TIME_TO_EXPIRY * (1. - DELTA);
final double[] upVOLS = new double[nVols];
final double[] dwVOLS = new double[nVols];
final double[] upInt = new double[nInt];
final double[] dwInt = new double[nInt];
for (int i = 0; i < nStrikes; ++i) {
upStrikes[i] = STRIKES_INPUT[i] * (1. + DELTA);
dwStrikes[i] = STRIKES_INPUT[i] * (1. - DELTA);
}
for (int i = 0; i < nVols; ++i) {
upVOLS[i] = VOLS[i] * (1. + DELTA);
dwVOLS[i] = VOLS[i] * (1. - DELTA);
}
for (int i = 0; i < nInt; ++i) {
upInt[i] = INTEREST_RATES[i] == 0. ? DELTA : INTEREST_RATES[i] * (1. + DELTA);
dwInt[i] = INTEREST_RATES[i] == 0. ? -DELTA : INTEREST_RATES[i] * (1. - DELTA);
}
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
for (int l = 0; l < nDiv; ++l) {
for (int m = 0; m < nDivT; ++m) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + l + "\t" + m);
final int nPymnts = MULTI_DIVIDEND_TIMES[m].length;
final double[] divTimeUp = new double[nPymnts];
final double[] divTimeDw = new double[nPymnts];
for (int n = 0; n < nPymnts; ++n) {
divTimeUp[n] = MULTI_DIVIDEND_TIMES[m][n] + DELTA;
divTimeDw[n] = MULTI_DIVIDEND_TIMES[m][n] - DELTA;
}
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double[] greeks = MODEL.getPriceAdjoint(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double[] greeksUp = MODEL.getPriceAdjoint(upSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double[] greeksDw = MODEL.getPriceAdjoint(dwSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceSpotUp = MODEL.price(upSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceSpotDw = MODEL.price(dwSpot, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceStrikeUp = MODEL.price(SPOT, upStrikes[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceStrikeDw = MODEL.price(SPOT, dwStrikes[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceRateUp = MODEL.price(SPOT, STRIKES_INPUT[i], upInt[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceRateDw = MODEL.price(SPOT, STRIKES_INPUT[i], dwInt[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceTimeUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], upTime, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceTimeDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], dwTime, VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceDivTimeUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], divTimeUp);
final double priceDivTimeDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l], divTimeDw);
final double priceVolUp = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, upVOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceVolDw = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, dwVOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double deltaFin = 0.5 * (priceSpotUp - priceSpotDw) / SPOT / DELTA;
final double dualDeltaFin = 0.5 * (priceStrikeUp - priceStrikeDw) / STRIKES_INPUT[i] / DELTA;
final double rhoFin = INTEREST_RATES[k] == 0. ? 0.5 * (priceRateUp - priceRateDw) / DELTA : 0.5 * (priceRateUp - priceRateDw) / INTEREST_RATES[k] / DELTA;
final double thetaFin = 0.5 * (priceTimeUp - priceTimeDw) / TIME_TO_EXPIRY / DELTA;
final double thetaDivFin = 0.5 * (priceDivTimeUp - priceDivTimeDw) / DELTA;
final double vegaFin = 0.5 * (priceVolUp - priceVolDw) / VOLS[j] / DELTA;
final double gammaFin = 0.5 * (greeksUp[1] - greeksDw[1]) / SPOT / DELTA;
assertEquals(price, greeks[0], 1.e-12);
assertEquals(deltaFin, greeks[1], Math.max(DELTA, Math.abs(deltaFin) * DELTA));
assertEquals(dualDeltaFin, greeks[2], Math.max(DELTA, Math.abs(dualDeltaFin) * DELTA));
assertEquals(rhoFin, greeks[3], Math.max(DELTA, Math.abs(rhoFin) * DELTA));
assertEquals(thetaFin, greeks[4], Math.max(DELTA, Math.abs(thetaFin) * DELTA));
assertEquals(thetaDivFin, greeks[5], Math.max(DELTA, Math.abs(thetaDivFin) * DELTA));
assertEquals(vegaFin, greeks[6], Math.max(DELTA, Math.abs(vegaFin) * DELTA));
assertEquals(gammaFin, greeks[7], Math.max(DELTA, Math.abs(gammaFin) * DELTA));
}
}
}
}
}
}
/**
* Comparison to binomial tree
* The recombining tree becomes a poor approximation if the number of dividend payments before the expiry is more than one
*/
@Test
public void treeComparisonTest() {
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final int nDiv = MULTI_DIVIDENDS.length;
final int nDivT = MULTI_DIVIDEND_TIMES.length;
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
final int steps = 101;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
for (int l = 0; l < nDiv; ++l) {
for (int m = 0; m < nDivT - 1; ++m) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + l + "\t" + m);
if (MULTI_DIVIDENDS[l][2] != 0.) {
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], TIME_TO_EXPIRY, VOLS[j], MULTI_DIVIDENDS[l][2], MULTI_DIVIDEND_TIMES[m][2]);
final OptionFunctionProvider1D function = new AmericanVanillaOptionFunctionProvider(STRIKES_INPUT[i], TIME_TO_EXPIRY, steps, true);
final DividendFunctionProvider cashDividend = new CashDividendFunctionProvider(new double[] {MULTI_DIVIDEND_TIMES[m][2] }, new double[] {MULTI_DIVIDENDS[l][2] });
final BinomialTreeOptionPricingModel model = new BinomialTreeOptionPricingModel();
final double priceTree = model.getPrice(lattice, function, SPOT, VOLS[j], INTEREST_RATES[k], cashDividend);
assertEquals(priceTree, price, Math.max(2.e-2, priceTree * 5.e-2));
// System.out.println(price + "\t" + priceTree);
}
}
}
}
}
}
}
/**
* Implied volatility calculator is tested
*/
@Test
public void impliedVolMultiTest() {
final double[] localTimeSet = new double[] {TIME_TO_EXPIRY, MULTI_DIVIDEND_TIMES[1][0] - 1.e-4 };
final int nTimeSet = localTimeSet.length;
final int nStrikes = STRIKES_INPUT.length;
final int nVols = VOLS.length;
final int nInt = INTEREST_RATES.length;
final int nDiv = MULTI_DIVIDENDS.length;
final int nDivT = MULTI_DIVIDEND_TIMES.length;
for (int i = 0; i < nStrikes; ++i) {
for (int j = 0; j < nVols; ++j) {
for (int k = 0; k < nInt; ++k) {
for (int l = 0; l < nDiv; ++l) {
for (int m = 0; m < nDivT; ++m) {
for (int n = 0; n < nTimeSet; ++n) {
// System.out.println(i + "\t" + j + "\t" + k + "\t" + l + "\t" + m);
final double price = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], localTimeSet[n], VOLS[j], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double impliedVol = MODEL.impliedVolatility(price, SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], localTimeSet[n], MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
final double priceRe = MODEL.price(SPOT, STRIKES_INPUT[i], INTEREST_RATES[k], localTimeSet[n], impliedVol, MULTI_DIVIDENDS[l], MULTI_DIVIDEND_TIMES[m]);
/*
* Due to zero vega for small vols, as found in other American option models, comparing vol sometimes fails.
* Instead the resulting option price is tested.
*/
// assertEquals(VOLS[j], impliedVol, Math.max(DELTA, Math.abs(VOLS[j]) * DELTA));
assertEquals(price, priceRe, Math.max(price, Math.abs(price) * DELTA));
}
}
}
}
}
}
}
/**
*
*/
@Test
public void errorMultiTest() {
final double[] params = new double[] {100., 100., 0.9, 0.2, 20., 0.8 };
final int nParams = params.length;
for (int i = 0; i < nParams - 2; ++i) {
params[i] *= -1.;
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
params[i] *= -1.;
}
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], new double[] {params[0] + params[4] }, new double[] {params[5] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], new double[] {params[0] + params[4] }, new double[] {params[5] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
// try {
// MODEL.price(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5] + params[2] });
// throw new RuntimeException();
// } catch (Exception e) {
// assertTrue(e instanceof IllegalArgumentException);
// }
// try {
// MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5] + params[2] });
// throw new RuntimeException();
// } catch (Exception e) {
// assertTrue(e instanceof IllegalArgumentException);
// }
try {
MODEL.impliedVolatility(params[0] / 10., params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5] + params[2] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.price(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5], params[2] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.getPriceAdjoint(params[0], params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5], params[2] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
try {
MODEL.impliedVolatility(params[0] / 10., params[1], 0.05, params[2], params[3], new double[] {params[4] }, new double[] {params[5], params[2] });
throw new RuntimeException();
} catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException);
}
}
/**
* Tests below are for debugging
*/
@Test(enabled = false)
public void simpleTest() {
final double spot = 50.;
final double strike = 50.;
final double timeToExpiry = 90. / 365.;
final double vol = 0.36;
final double div = 2.;
final double divTime = 75. / 365.;
final double rate = 0.05;
final RollGeskeWhaleyModel model = new RollGeskeWhaleyModel();
final double price = model.price(spot, strike, rate, timeToExpiry, vol, div, divTime);
System.out.println(model.price(spot, strike, rate, timeToExpiry, vol, div, divTime));
System.out.println(model.impliedVolatility(price, spot, strike, rate, timeToExpiry, div, divTime));
final BjerksundStenslandModel bjs = new BjerksundStenslandModel();
System.out.println(bjs.impliedVolatility(price, spot - div * Math.exp(-rate * divTime), strike, rate, rate, timeToExpiry, true));
final double[] greeks = model.getPriceAdjoint(spot, strike, rate, timeToExpiry, vol, div, divTime);
System.out.println(price + "\t" + greeks[0]);
}
/**
*
*/
@Test(enabled = false)
public void mktDataTest() {
final double spot = 56.144999999999996;
final double price = 16.225;
final double time = 0.10410958904109589;
final double strike = 40.;
final double rate = 0.0024352885167853145;
final double div = 0.38;
final double divTime = 0.06027397260273973;
final double quantity = -150;
final double vol = MODEL.impliedVolatility(price, spot, strike, rate, time, div, divTime);
final double[] greeks = MODEL.getPriceAdjoint(spot, strike, rate, time, vol, div, divTime);
final double volRe = MODEL.impliedVolatility(16.25, 56.19, strike, rate, time, div, divTime);
final double[] greeksRe = MODEL.getPriceAdjoint(56.19, strike, rate, time, volRe, div, divTime);
System.out.println(vol + "\t" + volRe);
System.out.println(greeks[0] + "\t" + greeksRe[0]);
System.out.println(greeks[1] + "\t" + greeksRe[1]);
System.out.println(greeks[2] + "\t" + greeksRe[2]);
System.out.println(greeks[3] + "\t" + greeksRe[3]);
System.out.println(greeks[4] + "\t" + greeksRe[4]);
System.out.println(greeks[5] * quantity + "\t" + greeksRe[5] * quantity);
System.out.println(greeks[6] * quantity + "\t" + greeksRe[6] * quantity);
// System.out.println("\n");
// for (int i = 0; i < 150; ++i) {
// final double vols = 0.01 + 0.005 * i;
// System.out.println(vols + "\t" + (MODEL.price(spot, strike, rate, time, vols, div, divTime) - price));
// }
}
}