/**
* Copyright (C) 2009 - 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.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertTrue;
import org.testng.annotations.Test;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.option.definition.AmericanVanillaOptionDefinition;
import com.opengamma.analytics.financial.model.option.definition.BinomialOptionModelDefinition;
import com.opengamma.analytics.financial.model.option.definition.EuropeanVanillaOptionDefinition;
import com.opengamma.analytics.financial.model.option.definition.OptionDefinition;
import com.opengamma.analytics.financial.model.option.definition.StandardOptionDataBundle;
import com.opengamma.analytics.financial.model.tree.RecombiningBinomialTree;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurface;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.surface.ConstantDoublesSurface;
import com.opengamma.util.time.DateUtils;
import com.opengamma.util.time.Expiry;
import com.opengamma.util.tuple.DoublesPair;
/**
*
*/
public class BinomialOptionModelTest {
private static final BinomialOptionModelDefinition<OptionDefinition, StandardOptionDataBundle> DUMMY = new BinomialOptionModelDefinition<OptionDefinition, StandardOptionDataBundle>() {
@Override
public double getDownFactor(final OptionDefinition option, final StandardOptionDataBundle data, final int n, final int j) {
return 1. / 1.1;
}
@Override
public RecombiningBinomialTree<Double> getUpProbabilityTree(final OptionDefinition option, final StandardOptionDataBundle data, final int n, final int j) {
final double t = option.getTimeToExpiry(data.getDate());
final double dt = t / n;
final double r = data.getInterestRate(t);
final double u = getUpFactor(option, data, n, j);
final double d = getDownFactor(option, data, n, j);
final double p = (Math.exp(r * dt) - d) / (u - d);
final Double[][] tree = new Double[n + 1][j];
for (int i = 0; i <= n; i++) {
for (int ii = 0; ii < j; ii++) {
tree[i][ii] = p;
}
}
return new RecombiningBinomialTree<>(tree);
}
@Override
public double getUpFactor(final OptionDefinition option, final StandardOptionDataBundle data, final int n, final int j) {
return 1.1;
}
};
private static final BinomialOptionModel<StandardOptionDataBundle> BINOMIAL_THREE_STEPS = new BinomialOptionModel<>(DUMMY, 3);
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNullDefinition() {
new BinomialOptionModel<>(null);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeN() {
new BinomialOptionModel<>(DUMMY, -3);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZeroN() {
new BinomialOptionModel<>(DUMMY, 0);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeDepth() {
new BinomialOptionModel<>(DUMMY, 3, -3);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testInconsistentDepth() {
new BinomialOptionModel<>(DUMMY, 3, 10);
}
@Test
public void testEuropeanCallTree() {
final ZonedDateTime date = DateUtils.getUTCDate(2009, 1, 1);
final StandardOptionDataBundle data = new StandardOptionDataBundle(YieldCurve.from(ConstantDoublesCurve.from(0.06)), 0., new VolatilitySurface(ConstantDoublesSurface.from(0.)), 100., date);
final OptionDefinition option = new EuropeanVanillaOptionDefinition(100, new Expiry(DateUtils.getDateOffsetWithYearFraction(date, 1)), true);
final Function1D<StandardOptionDataBundle, RecombiningBinomialTree<DoublesPair>> f = BINOMIAL_THREE_STEPS.getTreeGeneratingFunction(option);
final DoublesPair[][] result = f.evaluate(data).getNodes();
final DoublesPair[][] expected = new DoublesPair[4][4];
expected[0][0] = DoublesPair.of(100., 10.1457);
expected[1][0] = DoublesPair.of(90.91, 3.2545);
expected[1][1] = DoublesPair.of(110., 15.4471);
expected[2][0] = DoublesPair.of(82.64, 0.);
expected[2][1] = DoublesPair.of(100., 5.7048);
expected[2][2] = DoublesPair.of(121., 22.9801);
expected[3][0] = DoublesPair.of(75.13, 0.);
expected[3][1] = DoublesPair.of(90.91, 0.);
expected[3][2] = DoublesPair.of(110., 10.);
expected[3][3] = DoublesPair.of(133.1, 33.1);
assertTrees(expected, result, 4);
}
@Test
public void testAmericanPutTree() {
final ZonedDateTime date = DateUtils.getUTCDate(2009, 1, 1);
final StandardOptionDataBundle data = new StandardOptionDataBundle(YieldCurve.from(ConstantDoublesCurve.from(0.06)), 0., new VolatilitySurface(ConstantDoublesSurface.from(0.)), 100., date);
final OptionDefinition option = new AmericanVanillaOptionDefinition(100, new Expiry(DateUtils.getDateOffsetWithYearFraction(date, 1)), false);
final Function1D<StandardOptionDataBundle, RecombiningBinomialTree<DoublesPair>> f = BINOMIAL_THREE_STEPS.getTreeGeneratingFunction(option);
final DoublesPair[][] result = f.evaluate(data).getNodes();
final DoublesPair[][] expected = new DoublesPair[4][4];
expected[0][0] = DoublesPair.of(100., 4.6546);
expected[1][0] = DoublesPair.of(90.91, 9.2356);
expected[1][1] = DoublesPair.of(110., 1.5261);
expected[2][0] = DoublesPair.of(82.64, 17.3554);
expected[2][1] = DoublesPair.of(100., 3.7247);
expected[2][2] = DoublesPair.of(121., 0.);
expected[3][0] = DoublesPair.of(75.13, 24.8685);
expected[3][1] = DoublesPair.of(90.91, 9.0909);
expected[3][2] = DoublesPair.of(110., 0.);
expected[3][3] = DoublesPair.of(133.1, 0.);
assertTrees(expected, result, 4);
}
private void assertTrees(final DoublesPair[][] expected, final DoublesPair[][] result, final int n) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (expected[i][j] == null) {
assertTrue(result[i][j] == null);
} else {
assertEquals(expected[i][j].first, result[i][j].first, 1e-2);
assertEquals(expected[i][j].second, result[i][j].second, 1e-4);
}
}
}
}
}