/**
* 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.tree;
import static org.testng.Assert.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertTrue;
import org.apache.commons.lang.NotImplementedException;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.greeks.Greek;
import com.opengamma.analytics.financial.greeks.GreekResultCollection;
import com.opengamma.analytics.financial.model.volatility.BlackScholesFormulaRepository;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
import com.opengamma.util.test.TestGroup;
/**
* Test.
*/
@Test(groups = TestGroup.UNIT)
public class EuropeanSingleBarrierOptionFunctionProviderTest {
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
private static final BinomialTreeOptionPricingModel _model = new BinomialTreeOptionPricingModel();
private static final TrinomialTreeOptionPricingModel _modelTrinomial = new TrinomialTreeOptionPricingModel();
private static final double SPOT = 105.;
private static final double[] STRIKES = new double[] {97., 105., 114. };
private static final double TIME = 4.2;
private static final double[] INTERESTS = new double[] {0.015, 0.05 };
private static final double[] DIVIDENDS = new double[] {0.005, 0.02 };
/**
*
*/
@Test
public void priceTrinomialTest() {
final LatticeSpecification lattice = new CoxRossRubinsteinLatticeSpecification();
final double[] vols = new double[] {0.02 };
final int nSteps = 3121;
final int nStepsAdm = 165;
final double[] barrierSet = new double[] {90., 112. };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
for (final double dividend : DIVIDENDS) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
double exact = price(SPOT, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double res = _modelTrinomial.getPrice(lattice, function, SPOT, vol, interest, dividend);
assertEquals(res, exact, Math.max(exact, 1.) * 1.e-2);
final OptionFunctionProvider1D functionAdm = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nStepsAdm, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final LatticeSpecification latticeAdm = new AdaptiveLatticeSpecification(functionAdm);
final double resAdm = _modelTrinomial.getPrice(latticeAdm, functionAdm, SPOT, vol, interest, dividend);
assertEquals(resAdm, exact, Math.max(exact, 1.) * 1.e-2);
}
}
}
}
}
}
}
}
/**
*
*/
@Test
public void greeksTrinomialTest() {
final double eps = 1.e-6;
final LatticeSpecification lattice = new CoxRossRubinsteinLatticeSpecification();
final double[] vols = new double[] {0.02 };
final int nSteps = 3121;
final double[] barrierSet = new double[] {90., 112. };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
for (final double dividend : DIVIDENDS) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final double price = price(SPOT, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpotUp = price(SPOT + eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpotDown = price(SPOT - eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpot2Up = price(SPOT + 2. * eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpot2Down = price(SPOT - 2. * eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceTimeUp = price(SPOT, strike, TIME + eps, vol, interest, dividend, isCall, barrier, type);
final double priceTimeDown = price(SPOT, strike, TIME - eps, vol, interest, dividend, isCall, barrier, type);
final double delta = 0.5 * (priceSpotUp - priceSpotDown) / eps;
final double deltaUp = 0.5 * (priceSpot2Up - price) / eps;
final double deltaDown = 0.5 * (price - priceSpot2Down) / eps;
final double gamma = 0.5 * (deltaUp - deltaDown) / eps;
final double theta = -0.5 * (priceTimeUp - priceTimeDown) / eps;
final GreekResultCollection res = _modelTrinomial.getGreeks(lattice, function, SPOT, vol, interest, dividend);
assertEquals(res.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-2);
assertEquals(res.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-2);
assertEquals(res.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(res.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
}
}
}
}
}
}
}
}
/**
*
*/
@Test
public void priceTest() {
/*
* Due to slow convergence, only one lattice is used in this test
*/
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
/*
* As expected, large vol and spot \sim barrier leads to poor accuracy since the effect of discreteness becomes large.
*/
final double[] vols = new double[] {0.02 };
final int nSteps = 3121;
final double[] barrierSet = new double[] {90., 112. };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
for (final double dividend : DIVIDENDS) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
double exact = price(SPOT, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
assertEquals(res, exact, Math.max(exact, 1.) * 1.e-2);
}
}
}
}
}
}
}
}
/**
*
*/
@Test
public void greeksTest() {
final double eps = 1.e-6;
/*
* Due to slow convergence, only one lattice is used in this test
*/
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
/*
* As expected, large vol and spot \sim barrier leads to poor accuracy since the effect of discreteness becomes large.
*/
final double[] vols = new double[] {0.02 };
final int nSteps = 3121;
final double[] barrierSet = new double[] {90, 112 };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
for (final double dividend : DIVIDENDS) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final double price = price(SPOT, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpotUp = price(SPOT + eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpotDown = price(SPOT - eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpot2Up = price(SPOT + 2. * eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceSpot2Down = price(SPOT - 2. * eps, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double priceTimeUp = price(SPOT, strike, TIME + eps, vol, interest, dividend, isCall, barrier, type);
final double priceTimeDown = price(SPOT, strike, TIME - eps, vol, interest, dividend, isCall, barrier, type);
final double delta = 0.5 * (priceSpotUp - priceSpotDown) / eps;
final double deltaUp = 0.5 * (priceSpot2Up - price) / eps;
final double deltaDown = 0.5 * (price - priceSpot2Down) / eps;
final double gamma = 0.5 * (deltaUp - deltaDown) / eps;
final double theta = -0.5 * (priceTimeUp - priceTimeDown) / eps;
final GreekResultCollection res = _model.getGreeks(lattice, function, SPOT, vol, interest, dividend);
assertEquals(res.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-2);
assertEquals(res.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-2);
assertEquals(res.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(res.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
}
}
}
}
}
}
}
}
/**
*
*/
@Test
public void discreteDividendPriceTest() {
/*
* Due to slow convergence, only one lattice is used in this test
*/
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
final double[] propDividends = new double[] {0.002, 0.001, 0.002 };
final double[] cashDividends = new double[] {.2, 1.1, .5 };
final double[] dividendTimes = new double[] {TIME / 6., TIME / 3., TIME / 2. };
final double[] vols = new double[] {0.02 };
final double[] barrierSet = new double[] {90, 112 };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final int nSteps = 3121;
final LatticeSpecification latticeTri = new TianLatticeSpecification();
final int nStepsTri = 121;
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final double resSpot = SPOT * (1. - propDividends[0]) * (1. - propDividends[1]) * (1. - propDividends[2]);
final double modSpot = SPOT - cashDividends[0] * Math.exp(-interest * dividendTimes[0]) - cashDividends[1] * Math.exp(-interest * dividendTimes[1]) - cashDividends[2] *
Math.exp(-interest * dividendTimes[2]);
final DividendFunctionProvider cashDividend = new CashDividendFunctionProvider(dividendTimes, cashDividends);
final DividendFunctionProvider propDividend = new ProportionalDividendFunctionProvider(dividendTimes, propDividends);
final double resMod = _model.getPrice(lattice, function, SPOT, vol, interest, cashDividend);
final double resRes = _model.getPrice(lattice, function, SPOT, vol, interest, propDividend);
double exactMod = price(modSpot, strike, TIME, vol, interest, 0., isCall, barrier, type);
assertEquals(resMod, exactMod, Math.max(exactMod, 1.) * 1.e-2);
double exactRes = price(resSpot, strike, TIME, vol, interest, 0., isCall, barrier, type);
assertEquals(resRes, exactRes, Math.max(exactRes, 1.) * 1.e-2);
final OptionFunctionProvider1D functionTri = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nStepsTri, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final double resTriCash = _modelTrinomial.getPrice(latticeTri, functionTri, SPOT, vol, interest, cashDividend);
final double resTriProp = _modelTrinomial.getPrice(latticeTri, functionTri, SPOT, vol, interest, propDividend);
assertEquals(resTriCash, exactMod, Math.max(exactMod, 1.) * 1.e-1);
assertEquals(resTriProp, exactRes, Math.max(exactRes, 1.) * 1.e-1);
}
}
}
}
}
}
}
/**
*
*/
@Test
public void discreteDividendGreeksTest() {
final double eps = 1.e-6;
/*
* Due to slow convergence, only one lattice is used in this test
*/
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
final int nSteps = 3121;
final double[] propDividends = new double[] {0.002, 0.001, 0.002 };
final double[] cashDividends = new double[] {.2, 1.1, .5 };
final double[] dividendTimes = new double[] {TIME / 6., TIME / 3., TIME / 2. };
final double[] vols = new double[] {0.02 };
final double[] barrierSet = new double[] {90, 112 };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final LatticeSpecification latticeTri = new TianLatticeSpecification();
final int nStepsTri = 2121;
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final OptionFunctionProvider1D functionTri = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nStepsTri, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
final double resSpot = SPOT * (1. - propDividends[0]) * (1. - propDividends[1]) * (1. - propDividends[2]);
final double modSpot = SPOT - cashDividends[0] * Math.exp(-interest * dividendTimes[0]) - cashDividends[1] * Math.exp(-interest * dividendTimes[1]) - cashDividends[2] *
Math.exp(-interest * dividendTimes[2]);
final DividendFunctionProvider cashDividend = new CashDividendFunctionProvider(dividendTimes, cashDividends);
final DividendFunctionProvider propDividend = new ProportionalDividendFunctionProvider(dividendTimes, propDividends);
{
final double price = price(modSpot, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpotUp = price(modSpot + eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpotDown = price(modSpot - eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpot2Up = price(modSpot + 2. * eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpot2Down = price(modSpot - 2. * eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceTimeUp = price(modSpot, strike, TIME + eps, vol, interest, 0., isCall, barrier, type);
final double priceTimeDown = price(modSpot, strike, TIME - eps, vol, interest, 0., isCall, barrier, type);
final double delta = 0.5 * (priceSpotUp - priceSpotDown) / eps;
final double deltaUp = 0.5 * (priceSpot2Up - price) / eps;
final double deltaDown = 0.5 * (price - priceSpot2Down) / eps;
final double gamma = 0.5 * (deltaUp - deltaDown) / eps;
final double theta = -0.5 * (priceTimeUp - priceTimeDown) / eps;
final GreekResultCollection res = _model.getGreeks(lattice, function, SPOT, vol, interest, cashDividend);
assertEquals(res.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-2);
assertEquals(res.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-1);
assertEquals(res.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(res.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
final GreekResultCollection resTri = _modelTrinomial.getGreeks(latticeTri, functionTri, SPOT, vol, interest, cashDividend);
assertEquals(resTri.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-2);
assertEquals(resTri.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-1);
assertEquals(resTri.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(resTri.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
}
{
final double price = price(resSpot, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpotUp = price(resSpot + eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpotDown = price(resSpot - eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpot2Up = price(resSpot + 2. * eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceSpot2Down = price(resSpot - 2. * eps, strike, TIME, vol, interest, 0., isCall, barrier, type);
final double priceTimeUp = price(resSpot, strike, TIME + eps, vol, interest, 0., isCall, barrier, type);
final double priceTimeDown = price(resSpot, strike, TIME - eps, vol, interest, 0., isCall, barrier, type);
final double delta = 0.5 * (priceSpotUp - priceSpotDown) / eps;
final double deltaUp = 0.5 * (priceSpot2Up - price) / eps;
final double deltaDown = 0.5 * (price - priceSpot2Down) / eps;
final double gamma = 0.5 * (deltaUp - deltaDown) / eps;
final double theta = -0.5 * (priceTimeUp - priceTimeDown) / eps;
final GreekResultCollection res = _model.getGreeks(lattice, function, SPOT, vol, interest, propDividend);
assertEquals(res.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-2);
assertEquals(res.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-2);
assertEquals(res.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(res.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
final GreekResultCollection resTri = _modelTrinomial.getGreeks(latticeTri, functionTri, SPOT, vol, interest, propDividend);
assertEquals(resTri.get(Greek.FAIR_PRICE), price, Math.max(price, 1.) * 1.e-1);
assertEquals(resTri.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-1);
assertEquals(resTri.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-1);
assertEquals(resTri.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-1);
}
}
}
}
}
}
}
}
/**
* non-constant volatility and interest rate
*/
@Test
public void timeVaryingVolTest() {
final LatticeSpecification lattice1 = new TimeVaryingLatticeSpecification();
final double[] time_set = new double[] {0.5, 1.2 };
final int steps = 801;
final double[] vol = new double[steps];
final double[] rate = new double[steps];
final double[] dividend = new double[steps];
final int stepsTri = 691;
final double[] volTri = new double[stepsTri];
final double[] rateTri = new double[stepsTri];
final double[] dividendTri = new double[stepsTri];
final double constA = 0.01;
final double constB = 0.001;
final double constC = 0.1;
final double constD = 0.05;
final boolean[] tfSet = new boolean[] {true, false };
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double time : time_set) {
for (int i = 0; i < steps; ++i) {
rate[i] = constA + constB * i * time / steps;
vol[i] = constC + constD * Math.sin(i * time / steps);
dividend[i] = 0.005;
}
for (int i = 0; i < stepsTri; ++i) {
rateTri[i] = constA + constB * i * time / steps;
volTri[i] = constC + constD * Math.sin(i * time / steps);
dividendTri[i] = 0.005;
}
final double rateRef = constA + 0.5 * constB * time;
final double volRef = Math.sqrt(constC * constC + 0.5 * constD * constD + 2. * constC * constD / time * (1. - Math.cos(time)) - constD * constD * 0.25 / time * Math.sin(2. * time));
final double[] barrierSet = new double[] {SPOT * 0.9, SPOT * 1.1 };
for (final double barrier : barrierSet) {
final OptionFunctionProvider1D functionBarrierDown = new EuropeanSingleBarrierOptionFunctionProvider(strike, time, steps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.DownAndOut);
final OptionFunctionProvider1D functionBarrierUp = new EuropeanSingleBarrierOptionFunctionProvider(strike, time, steps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.UpAndOut);
final double resPriceBarrierDown = _model.getPrice(functionBarrierDown, SPOT, vol, rate, dividend);
final GreekResultCollection resGreeksBarrierDown = _model.getGreeks(functionBarrierDown, SPOT, vol, rate, dividend);
final double resPriceConstBarrierDown = _model.getPrice(lattice1, functionBarrierDown, SPOT, volRef, rateRef, dividend[0]);
final GreekResultCollection resGreeksConstBarrierDown = _model.getGreeks(lattice1, functionBarrierDown, SPOT, volRef, rateRef, dividend[0]);
assertEquals(resPriceBarrierDown, resPriceConstBarrierDown, Math.max(Math.abs(resPriceConstBarrierDown), 0.1) * 1.e-1);
assertEquals(resGreeksBarrierDown.get(Greek.FAIR_PRICE), resGreeksConstBarrierDown.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.FAIR_PRICE)), 0.1) * 0.1);
assertEquals(resGreeksBarrierDown.get(Greek.DELTA), resGreeksConstBarrierDown.get(Greek.DELTA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.DELTA)), 0.1) * 0.1);
assertEquals(resGreeksBarrierDown.get(Greek.GAMMA), resGreeksConstBarrierDown.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.GAMMA)), 0.1) * 0.1);
assertEquals(resGreeksBarrierDown.get(Greek.THETA), resGreeksConstBarrierDown.get(Greek.THETA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.THETA)), 0.1));
final double resPriceBarrierUp = _model.getPrice(functionBarrierUp, SPOT, vol, rate, dividend);
final GreekResultCollection resGreeksBarrierUp = _model.getGreeks(functionBarrierUp, SPOT, vol, rate, dividend);
final double resPriceConstBarrierUp = _model.getPrice(lattice1, functionBarrierUp, SPOT, volRef, rateRef, dividend[0]);
final GreekResultCollection resGreeksConstBarrierUp = _model.getGreeks(lattice1, functionBarrierUp, SPOT, volRef, rateRef, dividend[0]);
assertEquals(resPriceBarrierUp, resPriceConstBarrierUp, Math.max(Math.abs(resPriceConstBarrierUp), 0.1) * 1.e-1);
assertEquals(resGreeksBarrierUp.get(Greek.FAIR_PRICE), resGreeksConstBarrierUp.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.FAIR_PRICE)), 0.1) * 0.1);
assertEquals(resGreeksBarrierUp.get(Greek.DELTA), resGreeksConstBarrierUp.get(Greek.DELTA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.DELTA)), 0.1) * 0.1);
assertEquals(resGreeksBarrierUp.get(Greek.GAMMA), resGreeksConstBarrierUp.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.GAMMA)), 0.1) * 0.1);
assertEquals(resGreeksBarrierUp.get(Greek.THETA), resGreeksConstBarrierUp.get(Greek.THETA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.THETA)), 0.1));
final OptionFunctionProvider1D functionBarrierDownTri = new EuropeanSingleBarrierOptionFunctionProvider(strike, time, stepsTri, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.DownAndOut);
final OptionFunctionProvider1D functionBarrierUpTri = new EuropeanSingleBarrierOptionFunctionProvider(strike, time, stepsTri, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.UpAndOut);
final double resPriceBarrierDownTri = _modelTrinomial.getPrice(functionBarrierDownTri, SPOT, volTri, rateTri, dividendTri);
final GreekResultCollection resGreeksBarrierDownTri = _modelTrinomial.getGreeks(functionBarrierDownTri, SPOT, volTri, rateTri, dividendTri);
assertEquals(resPriceBarrierDownTri, resPriceConstBarrierDown, Math.max(Math.abs(resPriceConstBarrierDown), 1.) * 1.e-1);
assertEquals(resGreeksBarrierDownTri.get(Greek.FAIR_PRICE), resGreeksConstBarrierDown.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.FAIR_PRICE)), 1.) * 0.1);
assertEquals(resGreeksBarrierDownTri.get(Greek.DELTA), resGreeksConstBarrierDown.get(Greek.DELTA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.DELTA)), 1.) * 0.1);
assertEquals(resGreeksBarrierDownTri.get(Greek.GAMMA), resGreeksConstBarrierDown.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.GAMMA)), 1.) * 0.1);
assertEquals(resGreeksBarrierDownTri.get(Greek.THETA), resGreeksConstBarrierDown.get(Greek.THETA), Math.max(Math.abs(resGreeksConstBarrierDown.get(Greek.THETA)), 1.));
final double resPriceBarrierUpTri = _modelTrinomial.getPrice(functionBarrierUpTri, SPOT, volTri, rateTri, dividendTri);
final GreekResultCollection resGreeksBarrierUpTri = _modelTrinomial.getGreeks(functionBarrierUpTri, SPOT, volTri, rateTri, dividendTri);
assertEquals(resPriceBarrierUpTri, resPriceConstBarrierUp, Math.max(Math.abs(resPriceConstBarrierUp), 1.) * 1.e-1);
assertEquals(resGreeksBarrierUpTri.get(Greek.FAIR_PRICE), resGreeksConstBarrierUp.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.FAIR_PRICE)), 1.) * 0.1);
assertEquals(resGreeksBarrierUpTri.get(Greek.DELTA), resGreeksConstBarrierUp.get(Greek.DELTA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.DELTA)), 1.) * 0.1);
assertEquals(resGreeksBarrierUpTri.get(Greek.GAMMA), resGreeksConstBarrierUp.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.GAMMA)), 1.) * 0.1);
assertEquals(resGreeksBarrierUpTri.get(Greek.THETA), resGreeksConstBarrierUp.get(Greek.THETA), Math.max(Math.abs(resGreeksConstBarrierUp.get(Greek.THETA)), 1.));
}
}
}
}
}
/**
*
*/
@Test
public void getBarrierTest() {
final EuropeanSingleBarrierOptionFunctionProvider function = new EuropeanSingleBarrierOptionFunctionProvider(STRIKES[2], 1., 101, true, 90.,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndOut"));
assertEquals(function.getBarrier(), 90.);
}
/**
*
*/
@SuppressWarnings("unused")
@Test(expectedExceptions = IllegalArgumentException.class)
public void negativeBarrierTest() {
new EuropeanSingleBarrierOptionFunctionProvider(STRIKES[2], 1., 101, true, -2., EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndOut"));
}
/**
*
*/
@SuppressWarnings("unused")
@Test(expectedExceptions = NotImplementedException.class)
public void downInBarrierTest() {
new EuropeanSingleBarrierOptionFunctionProvider(STRIKES[2], 1., 101, true, 90., EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndIn"));
}
/**
*
*/
@SuppressWarnings("unused")
@Test(expectedExceptions = NotImplementedException.class)
public void upInBarrierTest() {
new EuropeanSingleBarrierOptionFunctionProvider(STRIKES[2], 1., 101, true, 90., EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("UpAndIn"));
}
/**
*
*/
@Test
public void hashCodeEqualsTest() {
final EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes type = EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndOut");
final OptionFunctionProvider1D ref = new EuropeanSingleBarrierOptionFunctionProvider(100., 1., 53, true, 90., type);
final OptionFunctionProvider1D[] function = new OptionFunctionProvider1D[] {ref, new EuropeanSingleBarrierOptionFunctionProvider(100., 1., 53, true, 90., type),
new EuropeanSingleBarrierOptionFunctionProvider(100., 1., 53, true, 91., type),
new EuropeanSingleBarrierOptionFunctionProvider(100., 1., 53, true, 90., EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("UpAndOut")),
new EuropeanVanillaOptionFunctionProvider(100., 1., 53, true), null };
final int len = function.length;
for (int i = 0; i < len; ++i) {
if (ref.equals(function[i])) {
assertTrue(ref.hashCode() == function[i].hashCode());
}
}
for (int i = 0; i < len - 1; ++i) {
assertTrue(function[i].equals(ref) == ref.equals(function[i]));
}
assertFalse(ref.equals(new EuropeanSpreadOptionFunctionProvider(100., 1., 53, true)));
}
private double price(final double spot, final double strike, final double time, final double vol, final double interest, final double dividend, final boolean isCall, final double barrier,
final String type) {
double exact = 0.;
if (type == "DownAndOut") {
if (strike > barrier) {
exact = isCall ? getA(spot, strike, time, vol, interest, dividend, 1.) - getC(spot, strike, time, vol, interest, dividend, barrier, 1., 1.) : getA(
spot, strike, time, vol, interest, dividend, -1.) - getB(spot, strike, time, vol, interest, dividend, barrier, -1.) +
getC(spot, strike, time, vol, interest, dividend, barrier, -1., 1.) - getD(spot, strike, time, vol, interest, dividend, barrier, -1., 1.);
exact = exact < 0. ? 0. : exact;
exact = spot <= barrier ? 0. : exact;
} else {
exact = isCall ? getB(spot, strike, time, vol, interest, dividend, barrier, 1.) - getD(spot, strike, time, vol, interest, dividend, barrier, 1., 1.) : 0.;
exact = exact < 0. ? 0. : exact;
exact = spot <= barrier ? 0. : exact;
}
} else {
if (strike < barrier) {
exact = !isCall ? getA(spot, strike, time, vol, interest, dividend, -1.) - getC(spot, strike, time, vol, interest, dividend, barrier, -1., -1.) : getA(
spot, strike, time, vol, interest, dividend, 1.) - getB(spot, strike, time, vol, interest, dividend, barrier, 1.) +
getC(spot, strike, time, vol, interest, dividend, barrier, 1., -1.) - getD(spot, strike, time, vol, interest, dividend, barrier, 1., -1.);
exact = exact < 0. ? 0. : exact;
exact = spot >= barrier ? 0. : exact;
} else {
exact = !isCall ? getB(spot, strike, time, vol, interest, dividend, barrier, -1.) - getD(spot, strike, time, vol, interest, dividend, barrier, -1., -1.) : 0.;
exact = exact < 0. ? 0. : exact;
exact = spot >= barrier ? 0. : exact;
}
}
return exact;
}
/*
* Tests below are for debugging
*/
/**
* Showing slow convergence for non-small vol
*/
@Test(enabled = false)
public void price1Test() {
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
final double vol = 0.2;
final int nSteps = 298121;
final double barrier = 85;
String type = "DownAndOut";
final boolean isCall = true;
final double strike = 110.;
final double interest = 0.08;
final double dividend = 0.02;
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
double exact = price(SPOT, strike, TIME, vol, interest, dividend, isCall, barrier, type);
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
assertEquals(res, exact, Math.max(exact, 1.) * 1.e-3);
}
/**
*
*/
@SuppressWarnings("unused")
@Test(enabled = false)
public void printTest() {
final double barrier = 90.0;
final double strike = 105.1;
final double vol = 0.09;
final double interest = -0.01;
final double dividend = 0.0;
final boolean isCall = false;
for (int i = 0; i < 500; ++i) {
final int nSteps = 2001 + 6 * i;
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndOut"));
// final LatticeSpecification lattice = new TrigeorgisLatticeSpecification();
// final LatticeSpecification lattice = new CoxRossRubinsteinLatticeSpecification();
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
double exact = isCall ? getA(SPOT, strike, TIME, vol, interest, dividend, 1.) - getC(SPOT, strike, TIME, vol, interest, dividend, barrier, 1., 1.) : getA(
SPOT, strike, TIME, vol, interest, dividend, -1.) -
getB(SPOT, strike, TIME, vol, interest, dividend, barrier, -1.) +
getC(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., 1.) -
getD(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., 1.);
exact = exact < 0. ? 0. : exact;
exact = SPOT <= barrier ? 0. : exact;
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
System.out.println(nSteps + "\t" + (res - exact));
}
}
/**
*
*/
@Test(enabled = false)
public void priceLeisenReimerTest() {
final LatticeSpecification lattice = new LeisenReimerLatticeSpecification();
/*
* As expected, large vol and spot \sim barrier leads to poor accuracy since the effect of discreteness becomes large.
*/
final double[] vols = new double[] {0.02, 0.09 };
final double eps = 1.e-2;
final int nSteps = 1189;
final double[] barrierSet = new double[] {90, 121 };
final String[] typeSet = new String[] {"DownAndOut", "UpAndOut" };
final boolean[] tfSet = new boolean[] {true, false };
for (final double barrier : barrierSet) {
for (final String type : typeSet) {
for (final boolean isCall : tfSet) {
for (final double strike : STRIKES) {
for (final double interest : INTERESTS) {
for (final double vol : vols) {
for (final double dividend : DIVIDENDS) {
final OptionFunctionProvider1D function = new EuropeanSingleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, barrier,
EuropeanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type));
if (type == "DownAndOut") {
if (strike > barrier) {
double exact = isCall ? getA(SPOT, strike, TIME, vol, interest, dividend, 1.) - getC(SPOT, strike, TIME, vol, interest, dividend, barrier, 1., 1.) : getA(
SPOT, strike, TIME, vol, interest, dividend, -1.) -
getB(SPOT, strike, TIME, vol, interest, dividend, barrier, -1.) +
getC(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., 1.) -
getD(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., 1.);
exact = exact < 0. ? 0. : exact;
exact = SPOT <= barrier ? 0. : exact;
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
System.out.println(barrier + "\t" + strike + "\t" + vol + "\t" + interest + "\t" + dividend + "\t" + isCall);
assertEquals(res, exact, Math.max(exact, 1.) * eps);
} else {
double exact = isCall ? getB(SPOT, strike, TIME, vol, interest, dividend, barrier, 1.) - getD(SPOT, strike, TIME, vol, interest, dividend, barrier, 1., 1.) : 0.;
exact = exact < 0. ? 0. : exact;
exact = SPOT <= barrier ? 0. : exact;
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
// System.out.println(strike + "\t" + vol + "\t" + interest + "\t" + exact + "\t" + res);
assertEquals(res, exact, Math.max(exact, 1.) * eps);
}
} else {
if (strike < barrier) {
double exact = !isCall ? getA(SPOT, strike, TIME, vol, interest, dividend, -1.) - getC(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., -1.) : getA(
SPOT, strike, TIME, vol, interest, dividend, 1.) -
getB(SPOT, strike, TIME, vol, interest, dividend, barrier, 1.) +
getC(SPOT, strike, TIME, vol, interest, dividend, barrier, 1., -1.) -
getD(SPOT, strike, TIME, vol, interest, dividend, barrier, 1., -1.);
exact = exact < 0. ? 0. : exact;
exact = SPOT >= barrier ? 0. : exact;
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
// System.out.println(barrier + "\t" + strike + "\t" + vol + "\t" + interest + "\t" + exact + "\t" + res);
assertEquals(res, exact, Math.max(exact, 1.) * eps);
} else {
double exact = !isCall ? getB(SPOT, strike, TIME, vol, interest, dividend, barrier, -1.) - getD(SPOT, strike, TIME, vol, interest, dividend, barrier, -1., -1.) : 0.;
exact = exact < 0. ? 0. : exact;
exact = SPOT >= barrier ? 0. : exact;
final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend);
// System.out.println(strike + "\t" + vol + "\t" + interest + "\t" + exact + "\t" + res);
assertEquals(res, exact, Math.max(exact, 1.) * eps);
}
}
}
}
}
}
}
}
}
}
private double getA(final double spot, final double strike, final double time, final double vol, final double interest, final double dividend, final double phi) {
final boolean isCall = (phi == 1.);
return BlackScholesFormulaRepository.price(spot, strike, time, vol, interest, interest - dividend, isCall);
}
private double getB(final double spot, final double strike, final double time, final double vol, final double interest, final double dividend, final double barrier, final double phi) {
final double sigmaRootT = vol * Math.sqrt(time);
final double x2 = (Math.log(spot / barrier) + interest * time - dividend * time) / sigmaRootT + 0.5 * sigmaRootT;
final double x2M = x2 - sigmaRootT;
return phi * (spot * Math.exp(-dividend * time) * NORMAL.getCDF(phi * x2) - strike * Math.exp(-interest * time) * NORMAL.getCDF(phi * x2M));
}
private double getC(final double spot, final double strike, final double time, final double vol, final double interest, final double dividend, final double barrier, final double phi,
final double eta) {
final boolean isCall = (eta == 1.);
final double mu = (interest - dividend) / vol / vol - 0.5;
return phi * eta * BlackScholesFormulaRepository.price(barrier * barrier / spot, strike, time, vol, interest, interest - dividend, isCall) * Math.pow(barrier / spot, 2. * mu);
}
private double getD(final double spot, final double strike, final double time, final double vol, final double interest, final double dividend, final double barrier, final double phi,
final double eta) {
final double sigmaRootT = vol * Math.sqrt(time);
final double y2 = (Math.log(barrier / spot) + interest * time - dividend * time) / sigmaRootT + 0.5 * sigmaRootT;
final double y2M = y2 - sigmaRootT;
final double mu = (interest - dividend) / vol / vol - 0.5;
return phi *
(spot * Math.exp(-dividend * time) * Math.pow(barrier / spot, 2. * mu + 2.) * NORMAL.getCDF(eta * y2) - strike * Math.exp(-interest * time) * Math.pow(barrier / spot, 2. * mu) *
NORMAL.getCDF(eta * y2M));
}
}