/** * 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.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 DoubleBarrierOptionFunctionProviderTest { 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[] VOLS = new double[] {0.05, 0.1, 0.5 }; private static final double[] INTERESTS = new double[] {0.015, 0.05 }; private static final double[] DIVIDENDS = new double[] {0.005, 0.01 }; /** * */ @Test public void priceTestTrinomial() { final LatticeSpecification lattice = new TianLatticeSpecification(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 3585; final double lower = 85.; final double upper = 135.; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; 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 DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower, upper, DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); double exact = price(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double res = _modelTrinomial.getPrice(lattice, function, SPOT, vol, interest, dividend); assertEquals(res, exact, Math.max(exact, .1) * 1.e-1); } } } } } } /** * */ @Test public void greeksTrinomialTest() { final double eps = 1.e-6; final LatticeSpecification lattice = new TianLatticeSpecification(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 3585; final double lower = 85.; final double upper = 135.; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; 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 DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower, upper, DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); final double price = price(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double delta = delta(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double deltaSpotUp = delta(SPOT + eps, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double deltaSpotDown = delta(SPOT - eps, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double priceTimeUp = price(SPOT, strike, TIME + eps, vol, interest, dividend, isCall, lower, upper); final double priceTimeDown = price(SPOT, strike, TIME - eps, vol, interest, dividend, isCall, lower, upper); final double gamma = 0.5 * (deltaSpotUp - deltaSpotDown) / 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-1); 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); } } } } } } /** * */ @Test public void putCallSymmetryTest() { /* * Two sample lattices are checked */ final LatticeSpecification[] lattices = new LatticeSpecification[] {new CoxRossRubinsteinLatticeSpecification(), new LeisenReimerLatticeSpecification() }; final int steps = 401; final double lower = 90.; final double upper = 120.; DoubleBarrierOptionFunctionProvider.BarrierTypes out = DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut"); for (final double strike : STRIKES) { final OptionFunctionProvider1D functionCall = new DoubleBarrierOptionFunctionProvider(strike, TIME, steps, true, lower, upper, out); final OptionFunctionProvider1D functionPut = new DoubleBarrierOptionFunctionProvider(SPOT * SPOT / strike, TIME, steps, false, SPOT * SPOT / upper, SPOT * SPOT / lower, out); for (final double interest : INTERESTS) { for (final double vol : VOLS) { for (final double dividend : DIVIDENDS) { for (final LatticeSpecification lattice : lattices) { final GreekResultCollection greekCall = _model.getGreeks(lattice, functionCall, SPOT, vol, interest, dividend); final GreekResultCollection greekPut = _model.getGreeks(lattice, functionPut, SPOT, vol, dividend, interest); final double priceCall = greekCall.get(Greek.FAIR_PRICE); final double thetaCall = greekCall.get(Greek.THETA); final double pricePut = greekPut.get(Greek.FAIR_PRICE); final double thetaPut = greekPut.get(Greek.THETA); final double refPrice = strike * pricePut / SPOT; final double refTheta = strike * thetaPut / SPOT; assertEquals(priceCall, refPrice, Math.max(refPrice, 0.1) * 1.e-5); assertEquals(thetaCall, refTheta, Math.max(Math.abs(refTheta), 0.1) * 1.e-2); } } } } } } /** * */ @Test public void priceTest() { /* * Due to slow convergence, only one lattice is used in this test */ final LatticeSpecification lattice = new LeisenReimerLatticeSpecification(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 3321; final double lower = 85.; final double upper = 135.; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; 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 DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower, upper, DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); double exact = price(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend); assertEquals(res, exact, Math.max(exact, .1) * 1.e-1); } } } } } } /** * */ @Test public void priceOutOfRangeTest() { final LatticeSpecification lattice = new LeisenReimerLatticeSpecification(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 11; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; for (final boolean isCall : tfSet) { final double strike = isCall ? 115. : 95.; final double[] lower = new double[] {85., 112., isCall ? 80. : 96. }; final double[] upper = new double[] {101., 130., isCall ? 114. : 130. }; for (final double interest : INTERESTS) { for (final double vol : vols) { for (final double dividend : DIVIDENDS) { for (int i = 0; i < lower.length; ++i) { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); final double res = _model.getPrice(lattice, function, SPOT, vol, interest, dividend); assertEquals(res, 0.); } } } } } } /** * */ @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(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 3321; final double lower = 85.; final double upper = 135.; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; 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 DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower, upper, DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); final double price = price(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double delta = delta(SPOT, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double deltaSpotUp = delta(SPOT + eps, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double deltaSpotDown = delta(SPOT - eps, strike, TIME, vol, interest, dividend, isCall, lower, upper); final double priceTimeUp = price(SPOT, strike, TIME + eps, vol, interest, dividend, isCall, lower, upper); final double priceTimeDown = price(SPOT, strike, TIME - eps, vol, interest, dividend, isCall, lower, upper); final double gamma = 0.5 * (deltaSpotUp - deltaSpotDown) / 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-1); 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); } } } } } } /** * */ @Test public void greeksOutOfRangeTest() { final double eps = 1.e-6; /* * Due to slow convergence, only one lattice is used in this test */ final LatticeSpecification lattice = new LeisenReimerLatticeSpecification(); final double[] vols = new double[] {0.15, 0.25 }; final int nSteps = 21; final boolean[] tfSet = new boolean[] {true, false }; final String type = "DoubleKnockOut"; for (final boolean isCall : tfSet) { final double strike = isCall ? 115. : 95.; final double[] lower = new double[] {85., 112., isCall ? 80. : 96. }; final double[] upper = new double[] {101., 130., isCall ? 114. : 130. }; for (final double interest : INTERESTS) { for (final double vol : vols) { for (final double dividend : DIVIDENDS) { for (int i = 0; i < lower.length; ++i) { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(strike, TIME, nSteps, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf(type)); final double price = price(SPOT, strike, TIME, vol, interest, dividend, isCall, lower[i], upper[i]); final double delta = delta(SPOT, strike, TIME, vol, interest, dividend, isCall, lower[i], upper[i]); final double deltaSpotUp = delta(SPOT + eps, strike, TIME, vol, interest, dividend, isCall, lower[i], upper[i]); final double deltaSpotDown = delta(SPOT - eps, strike, TIME, vol, interest, dividend, isCall, lower[i], upper[i]); final double priceTimeUp = price(SPOT, strike, TIME + eps, vol, interest, dividend, isCall, lower[i], upper[i]); final double priceTimeDown = price(SPOT, strike, TIME - eps, vol, interest, dividend, isCall, lower[i], upper[i]); final double gamma = 0.5 * (deltaSpotUp - deltaSpotDown) / 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-3); assertEquals(res.get(Greek.DELTA), delta, Math.max(delta, 1.) * 1.e-3); assertEquals(res.get(Greek.GAMMA), gamma, Math.max(gamma, 1.) * 1.e-3); assertEquals(res.get(Greek.THETA), theta, Math.max(theta, 1.) * 1.e-3); } } } } } } /** * */ @Test public void binomialTrinomialDiscreteDividendTest() { final LatticeSpecification lattice = new TianLatticeSpecification(); final double time = 5.; final double[] propDividends = new double[] {0.05, 0.06, 0.05 }; final double[] cashDividends = new double[] {3., 2., 4.5 }; final double[] dividendTimes = new double[] {time / 6., time / 3., time / 2. }; final DividendFunctionProvider cashDividend = new CashDividendFunctionProvider(dividendTimes, cashDividends); final DividendFunctionProvider propDividend = new ProportionalDividendFunctionProvider(dividendTimes, propDividends); final int steps = 139; final int stepsTri = 114; final boolean[] tfSet = new boolean[] {true, false }; for (final boolean isCall : tfSet) { final double strike = isCall ? 115. : 95.; final double[] lower = new double[] {85., isCall ? 95. : 80. }; final double[] upper = new double[] {125., isCall ? 130. : 115. }; for (final double interest : INTERESTS) { for (final double vol : VOLS) { for (int i = 0; i < lower.length; ++i) { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(strike, TIME, steps, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); final double priceCash = _model.getPrice(lattice, function, SPOT, vol, interest, cashDividend); final double priceProp = _model.getPrice(lattice, function, SPOT, vol, interest, propDividend); final GreekResultCollection resCash = _model.getGreeks(lattice, function, SPOT, vol, interest, cashDividend); final GreekResultCollection resProp = _model.getGreeks(lattice, function, SPOT, vol, interest, propDividend); final OptionFunctionProvider1D functionTri = new DoubleBarrierOptionFunctionProvider(strike, TIME, stepsTri, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); final double priceCashTrinomial = _modelTrinomial.getPrice(lattice, functionTri, SPOT, vol, interest, cashDividend); final double pricePropTrinomial = _modelTrinomial.getPrice(lattice, functionTri, SPOT, vol, interest, propDividend); final GreekResultCollection resCashTrinomial = _modelTrinomial.getGreeks(lattice, functionTri, SPOT, vol, interest, cashDividend); final GreekResultCollection resPropTrinomial = _modelTrinomial.getGreeks(lattice, functionTri, SPOT, vol, interest, propDividend); assertEquals(resCash.get(Greek.FAIR_PRICE), priceCash, 1.e-14); assertEquals(resCashTrinomial.get(Greek.FAIR_PRICE), priceCashTrinomial, 1.e-14); assertEquals(resCash.get(Greek.FAIR_PRICE), resCashTrinomial.get(Greek.FAIR_PRICE), Math.max(Math.abs(resCashTrinomial.get(Greek.FAIR_PRICE)), 1.) * 1.e-1); assertEquals(resCash.get(Greek.DELTA), resCashTrinomial.get(Greek.DELTA), Math.max(Math.abs(resCashTrinomial.get(Greek.DELTA)), 1.) * 1.e-1); assertEquals(resCash.get(Greek.GAMMA), resCashTrinomial.get(Greek.GAMMA), Math.max(Math.abs(resCashTrinomial.get(Greek.GAMMA)), 1.) * 1.e-1); assertEquals(resCash.get(Greek.THETA), resCashTrinomial.get(Greek.THETA), Math.max(Math.abs(resCashTrinomial.get(Greek.THETA)), 1.) * 1.); assertEquals(resProp.get(Greek.FAIR_PRICE), priceProp, 1.e-14); assertEquals(resPropTrinomial.get(Greek.FAIR_PRICE), pricePropTrinomial, 1.e-14); assertEquals(resProp.get(Greek.FAIR_PRICE), resPropTrinomial.get(Greek.FAIR_PRICE), Math.max(Math.abs(resPropTrinomial.get(Greek.FAIR_PRICE)), 1.) * 1.e-1); assertEquals(resProp.get(Greek.DELTA), resPropTrinomial.get(Greek.DELTA), Math.max(Math.abs(resPropTrinomial.get(Greek.DELTA)), 1.) * 1.e-1); assertEquals(resProp.get(Greek.GAMMA), resPropTrinomial.get(Greek.GAMMA), Math.max(Math.abs(resPropTrinomial.get(Greek.GAMMA)), 1.) * 1.e-1); assertEquals(resProp.get(Greek.THETA), resPropTrinomial.get(Greek.THETA), Math.max(Math.abs(resPropTrinomial.get(Greek.THETA)), 1.) * 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 = 701; final double[] vol = new double[steps]; final double[] rate = new double[steps]; final double[] dividend = new double[steps]; final int stepsTri = 621; 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) { final double strike = isCall ? 115. : 95.; final double[] lower = new double[] {85., isCall ? 95. : 80. }; final double[] upper = new double[] {125., isCall ? 130. : 115. }; 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)); for (int i = 0; i < lower.length; ++i) { final OptionFunctionProvider1D functionVanilla = new DoubleBarrierOptionFunctionProvider(strike, TIME, steps, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); final double resPrice = _model.getPrice(functionVanilla, SPOT, vol, rate, dividend); final GreekResultCollection resGreeks = _model.getGreeks(functionVanilla, SPOT, vol, rate, dividend); final double resPriceConst = _model.getPrice(lattice1, functionVanilla, SPOT, volRef, rateRef, dividend[0]); final GreekResultCollection resGreeksConst = _model.getGreeks(lattice1, functionVanilla, SPOT, volRef, rateRef, dividend[0]); assertEquals(resPrice, resPriceConst, Math.max(Math.abs(resPriceConst), .1) * 1.e-1); assertEquals(resGreeks.get(Greek.FAIR_PRICE), resGreeksConst.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConst.get(Greek.FAIR_PRICE)), 0.1) * 0.1); assertEquals(resGreeks.get(Greek.DELTA), resGreeksConst.get(Greek.DELTA), Math.max(Math.abs(resGreeksConst.get(Greek.DELTA)), 0.1) * 0.1); assertEquals(resGreeks.get(Greek.GAMMA), resGreeksConst.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConst.get(Greek.GAMMA)), 0.1) * 0.1); assertEquals(resGreeks.get(Greek.THETA), resGreeksConst.get(Greek.THETA), Math.max(Math.abs(resGreeksConst.get(Greek.THETA)), 0.1)); final OptionFunctionProvider1D functionTri = new DoubleBarrierOptionFunctionProvider(strike, TIME, stepsTri, isCall, lower[i], upper[i], DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); final double resPriceTrinomial = _modelTrinomial.getPrice(functionTri, SPOT, volTri, rateTri, dividendTri); assertEquals(resPriceTrinomial, resPriceConst, Math.max(Math.abs(resPriceConst), 1.) * 1.e-1); final GreekResultCollection resGreeksTrinomial = _modelTrinomial.getGreeks(functionTri, SPOT, volTri, rateTri, dividendTri); assertEquals(resGreeksTrinomial.get(Greek.FAIR_PRICE), resGreeksConst.get(Greek.FAIR_PRICE), Math.max(Math.abs(resGreeksConst.get(Greek.FAIR_PRICE)), 1.) * 0.1); assertEquals(resGreeksTrinomial.get(Greek.DELTA), resGreeksConst.get(Greek.DELTA), Math.max(Math.abs(resGreeksConst.get(Greek.DELTA)), 1.) * 0.1); assertEquals(resGreeksTrinomial.get(Greek.GAMMA), resGreeksConst.get(Greek.GAMMA), Math.max(Math.abs(resGreeksConst.get(Greek.GAMMA)), 1.) * 0.1); assertEquals(resGreeksTrinomial.get(Greek.THETA), resGreeksConst.get(Greek.THETA), Math.max(Math.abs(resGreeksConst.get(Greek.THETA)), 1.)); } } } } /** * */ @Test public void getLowerBarrierTest() { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 88., 121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); assertEquals(((DoubleBarrierOptionFunctionProvider) function).getLowerBarrier(), 88.); } /** * */ @Test public void getUpperBarrierTest() { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 88., 121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); assertEquals(((DoubleBarrierOptionFunctionProvider) function).getUpperBarrier(), 121.); } /** * */ @Test(expectedExceptions = IllegalArgumentException.class) public void getUnspecifiedBarrierTest() { final OptionFunctionProvider1D function = new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 88., 121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); ((DoubleBarrierOptionFunctionProvider) function).getBarrier(); } /** * */ @SuppressWarnings("unused") @Test(expectedExceptions = NotImplementedException.class) public void DoubleKnockInTest() { new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 88., 121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockIn")); } /** * */ @SuppressWarnings("unused") @Test(expectedExceptions = IllegalArgumentException.class) public void negativeUpperBarrierTest() { new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 88., -121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); } /** * */ @SuppressWarnings("unused") @Test(expectedExceptions = IllegalArgumentException.class) public void smallUpperBarrierTest() { new DoubleBarrierOptionFunctionProvider(100., TIME, 21, true, 188., 121., DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut")); } /** * */ @Test public void hashCodeEqualsTest() { final DoubleBarrierOptionFunctionProvider.BarrierTypes type = DoubleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DoubleKnockOut"); final AmericanSingleBarrierOptionFunctionProvider.BarrierTypes type1 = AmericanSingleBarrierOptionFunctionProvider.BarrierTypes.valueOf("DownAndOut"); final OptionFunctionProvider1D ref = new DoubleBarrierOptionFunctionProvider(100., 1., 53, true, 90., 120., type); final OptionFunctionProvider1D[] function = new OptionFunctionProvider1D[] {ref, new DoubleBarrierOptionFunctionProvider(100., 1., 53, true, 90., 120., type), new DoubleBarrierOptionFunctionProvider(100., 1., 53, true, 90., 121., type), new AmericanSingleBarrierOptionFunctionProvider(100., 1., 53, true, 90., type1), new AmericanVanillaOptionFunctionProvider(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 div, final boolean isCall, final double lower, final double upper) { final int rg = 4; final double sigmaRootT = vol * Math.sqrt(time); final double second = (interest - div + 0.5 * vol * vol) * time; final double mu = 2. * (interest - div) / vol / vol + 1.; if (spot <= lower || spot >= upper) { return 0.; } double tmp1 = 0.; double tmp2 = 0.; if (isCall) { for (int i = -rg; i < rg + 1; ++i) { final double d1 = (Math.log(spot * Math.pow(upper, 2. * i) / strike / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double d2 = (Math.log(spot * Math.pow(upper, 2. * i - 1) / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double d3 = (Math.log(Math.pow(lower, 2. * i + 2) / spot / strike / Math.pow(upper, 2. * i)) + second) / sigmaRootT; final double d4 = (Math.log(Math.pow(lower, 2. * i + 2.) / spot / Math.pow(upper, 2. * i + 1.)) + second) / sigmaRootT; tmp1 += Math.pow(upper / lower, i * mu) * (NORMAL.getCDF(d1) - NORMAL.getCDF(d2)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getCDF(d3) - NORMAL.getCDF(d4)); tmp2 += Math.pow(upper / lower, i * mu - i * 2.) * (NORMAL.getCDF(d1 - sigmaRootT) - NORMAL.getCDF(d2 - sigmaRootT)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getCDF(d3 - sigmaRootT) - NORMAL.getCDF(d4 - sigmaRootT)); } final double res = tmp1 * spot * Math.exp(-div * time) - tmp2 * strike * Math.exp(-interest * time); return res <= 0. ? 0. : res; } for (int i = -rg; i < rg + 1; ++i) { final double y1 = (Math.log(spot * Math.pow(upper, 2. * i) / Math.pow(lower, 2. * i + 1.)) + second) / sigmaRootT; final double y2 = (Math.log(spot * Math.pow(upper, 2. * i) / strike / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double y3 = (Math.log(Math.pow(lower, 2. * i + 1.) / spot / Math.pow(upper, 2. * i)) + second) / sigmaRootT; final double y4 = (Math.log(Math.pow(lower, 2. * i + 2.) / spot / strike / Math.pow(upper, 2. * i)) + second) / sigmaRootT; tmp2 += Math.pow(upper / lower, i * mu - i * 2.) * (NORMAL.getCDF(y1 - sigmaRootT) - NORMAL.getCDF(y2 - sigmaRootT)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getCDF(y3 - sigmaRootT) - NORMAL.getCDF(y4 - sigmaRootT)); tmp1 += Math.pow(upper / lower, i * mu) * (NORMAL.getCDF(y1) - NORMAL.getCDF(y2)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getCDF(y3) - NORMAL.getCDF(y4)); } final double res = tmp2 * strike * Math.exp(-interest * time) - tmp1 * spot * Math.exp(-div * time); return res <= 0. ? 0. : res; } private double delta(final double spot, final double strike, final double time, final double vol, final double interest, final double div, final boolean isCall, final double lower, final double upper) { final int rg = 4; final double sigmaRootT = vol * Math.sqrt(time); final double second = (interest - div + 0.5 * vol * vol) * time; final double mu = 2. * (interest - div) / vol / vol + 1.; if (spot < lower || spot > upper) { return 0.; } double tmp1 = 0.; double tmp1Diff = 0.; double tmp2Diff = 0.; if (isCall) { final double d12Diff = 1. / spot / sigmaRootT; final double d34Diff = -d12Diff; for (int i = -rg; i < rg + 1; ++i) { final double d1 = (Math.log(spot * Math.pow(upper, 2. * i) / strike / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double d2 = (Math.log(spot * Math.pow(upper, 2. * i - 1) / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double d3 = (Math.log(Math.pow(lower, 2. * i + 2) / spot / strike / Math.pow(upper, 2. * i)) + second) / sigmaRootT; final double d4 = (Math.log(Math.pow(lower, 2. * i + 2.) / spot / Math.pow(upper, 2. * i + 1.)) + second) / sigmaRootT; tmp1 += Math.pow(upper / lower, i * mu) * (NORMAL.getCDF(d1) - NORMAL.getCDF(d2)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getCDF(d3) - NORMAL.getCDF(d4)); tmp1Diff += Math.pow(upper / lower, i * mu) * (NORMAL.getPDF(d1) - NORMAL.getPDF(d2)) * d12Diff - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getPDF(d3) - NORMAL.getPDF(d4)) * d34Diff + mu * Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) / spot * (NORMAL.getCDF(d3) - NORMAL.getCDF(d4)); tmp2Diff += Math.pow(upper / lower, i * mu - i * 2.) * (NORMAL.getPDF(d1 - sigmaRootT) - NORMAL.getPDF(d2 - sigmaRootT)) * d12Diff - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getPDF(d3 - sigmaRootT) - NORMAL.getPDF(d4 - sigmaRootT)) * d34Diff + (mu - 2.) / spot * Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getCDF(d3 - sigmaRootT) - NORMAL.getCDF(d4 - sigmaRootT)); } final double res = tmp1 * Math.exp(-div * time) + tmp1Diff * spot * Math.exp(-div * time) - tmp2Diff * strike * Math.exp(-interest * time); return res; } final double y12Diff = 1. / spot / sigmaRootT; final double y34Diff = -y12Diff; for (int i = -rg; i < rg + 1; ++i) { final double y1 = (Math.log(spot * Math.pow(upper, 2. * i) / Math.pow(lower, 2. * i + 1.)) + second) / sigmaRootT; final double y2 = (Math.log(spot * Math.pow(upper, 2. * i) / strike / Math.pow(lower, 2. * i)) + second) / sigmaRootT; final double y3 = (Math.log(Math.pow(lower, 2. * i + 1.) / spot / Math.pow(upper, 2. * i)) + second) / sigmaRootT; final double y4 = (Math.log(Math.pow(lower, 2. * i + 2.) / spot / strike / Math.pow(upper, 2. * i)) + second) / sigmaRootT; tmp2Diff += Math.pow(upper / lower, i * mu - i * 2.) * (NORMAL.getPDF(y1 - sigmaRootT) - NORMAL.getPDF(y2 - sigmaRootT)) * y12Diff - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getPDF(y3 - sigmaRootT) - NORMAL.getPDF(y4 - sigmaRootT)) * y34Diff + (mu - 2.) / spot * Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu - 2.) * (NORMAL.getCDF(y3 - sigmaRootT) - NORMAL.getCDF(y4 - sigmaRootT)); tmp1Diff += Math.pow(upper / lower, i * mu) * (NORMAL.getPDF(y1) - NORMAL.getPDF(y2)) * y12Diff - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getPDF(y3) - NORMAL.getPDF(y4)) * y34Diff + mu / spot * Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getCDF(y3) - NORMAL.getCDF(y4)); tmp1 += Math.pow(upper / lower, i * mu) * (NORMAL.getCDF(y1) - NORMAL.getCDF(y2)) - Math.pow(Math.pow(lower, i + 1.) / Math.pow(upper, i) / spot, mu) * (NORMAL.getCDF(y3) - NORMAL.getCDF(y4)); } final double res = tmp2Diff * strike * Math.exp(-interest * time) - tmp1Diff * spot * Math.exp(-div * time) - tmp1 * Math.exp(-div * time); return res; } /** * test for analytic formula */ @Test(enabled = false) public void functionTest() { final boolean[] tfSet = new boolean[] {true, false }; final double eps = 1.e-6; final double lower = 85.; final double upper = 135.; 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 double delta = delta(SPOT, strike, TIME, vol, interest, interest - dividend, isCall, lower, upper); final double upSpot = price(SPOT + eps, strike, TIME, vol, interest, interest - dividend, isCall, lower, upper); final double downSpot = price(SPOT - eps, strike, TIME, vol, interest, interest - dividend, isCall, lower, upper); assertEquals(delta, 0.5 * (upSpot - downSpot) / eps, eps); } } } } } } // @Test // public void test() { // final double spot = 100.; // final double strike = 100.; // final double interest = 0.1; // final double div = 0.; // // final double[] vol = new double[] {0.15, 0.25, 0.35 }; // final double[] time = new double[] {0.25, 0.5 }; // final double[] upper = new double[] {150., 140., 130., 120., 110. }; // final double[] lower = new double[] {50., 60., 70., 80., 90. }; // // for (int i = 0; i < upper.length; ++i) { // for (int j = 0; j < time.length; ++j) { // for (int k = 0; k < vol.length; ++k) { // final double callPrice = price(spot, strike, time[j], vol[k], interest, div, true, upper[i], lower[i]); // System.out.print(callPrice + "\t"); // final double dual = price(strike, spot, time[j], vol[k], div, interest, false, spot * strike / lower[i], spot * strike / upper[i]); // assertEquals(callPrice, dual, Math.max(callPrice, 1.) * 1.e-10); // // } // } // System.out.print("\n"); // } // // } }