/** * 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 com.google.common.primitives.Doubles; import com.opengamma.analytics.financial.greeks.Greek; import com.opengamma.analytics.financial.greeks.GreekResultCollection; import com.opengamma.analytics.financial.model.option.definition.StandardOptionDataBundle; import com.opengamma.util.ArgumentChecker; /** * */ public class BinomialTreeOptionPricingModel extends TreeOptionPricingModel { @Override public double getPrice(final LatticeSpecification lattice, final OptionFunctionProvider1D function, final double spot, final double volatility, final double interestRate, final double dividend) { ArgumentChecker.notNull(lattice, "lattice"); ArgumentChecker.notNull(function, "function"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); ArgumentChecker.isTrue(volatility > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend), "dividend should be finite"); final LatticeSpecification modLattice = (lattice instanceof TimeVaryingLatticeSpecification) ? new TrigeorgisLatticeSpecification() : lattice; if (function instanceof BarrierOptionFunctionProvider) { final BarrierOptionFunctionProvider barrierFunction = (BarrierOptionFunctionProvider) function; if (barrierFunction.getChecker().checkOut(spot) || barrierFunction.getChecker().checkStrikeBehindBarrier()) { return 0.; } } final int nSteps = function.getNumberOfSteps(); final double strike = function.getStrike(); final double timeToExpiry = function.getTimeToExpiry(); final double dt = timeToExpiry / nSteps; final double discount = Math.exp(-interestRate * dt); final double[] params = modLattice.getParameters(spot, strike, timeToExpiry, volatility, interestRate - dividend, nSteps, dt); final double upFactor = params[0]; final double downFactor = params[1]; final double upProbability = params[2]; final double downProbability = params[3]; final double upOverDown = upFactor / downFactor; ArgumentChecker.isTrue(upProbability > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability < 1., "upProbability should be smaller than 1."); double[] values = function.getPayoffAtExpiry(spot, downFactor, upOverDown); for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(discount, upProbability, downProbability, values, spot, 0., downFactor, upOverDown, i); } return values[0]; } /* * Array is used for dividend to realize constant cost of carry given by b = r - q */ @Override public double getPrice(final OptionFunctionProvider1D function, final double spot, final double[] volatility, final double[] interestRate, final double[] dividend) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(volatility, "volatility"); ArgumentChecker.notNull(interestRate, "interestRate"); ArgumentChecker.notNull(dividend, "dividend"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); final TimeVaryingLatticeSpecification vLattice = new TimeVaryingLatticeSpecification(); final int nSteps = function.getNumberOfSteps(); final double timeToExpiry = function.getTimeToExpiry(); ArgumentChecker.isTrue(nSteps == interestRate.length, "Wrong interestRate length"); ArgumentChecker.isTrue(nSteps == volatility.length, "Wrong volatility length"); ArgumentChecker.isTrue(nSteps == dividend.length, "Wrong dividend length"); for (int i = 0; i < nSteps; ++i) { ArgumentChecker.isTrue(volatility[i] > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility[i]), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate[i]), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend[i]), "dividend should be finite"); } if (function instanceof BarrierOptionFunctionProvider) { final BarrierOptionFunctionProvider barrierFunction = (BarrierOptionFunctionProvider) function; if (barrierFunction.getChecker().checkOut(spot) || barrierFunction.getChecker().checkStrikeBehindBarrier()) { return 0.; } } final double[] nu = vLattice.getShiftedDrift(volatility, interestRate, dividend); final double spaceStep = vLattice.getSpaceStep(timeToExpiry, volatility, nSteps, nu); final double downFactor = Math.exp(-spaceStep); final double upOverDown = Math.exp(2. * spaceStep); final double[] upProbability = new double[nSteps]; final double[] downProbability = new double[nSteps]; final double[] df = new double[nSteps]; for (int i = 0; i < nSteps; ++i) { final double[] params = vLattice.getParameters(volatility[i], nu[i], spaceStep); upProbability[i] = params[1]; downProbability[i] = 1. - params[1]; df[i] = Math.exp(-interestRate[i] * params[0]); ArgumentChecker.isTrue(upProbability[i] > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability[i] < 1., "upProbability should be smaller than 1."); } double[] values = function.getPayoffAtExpiry(spot, downFactor, upOverDown); for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(df[i], upProbability[i], downProbability[i], values, spot, 0., downFactor, upOverDown, i); } return values[0]; } @Override public double getPrice(final LatticeSpecification lattice, final OptionFunctionProvider1D function, final double spot, final double volatility, final double interestRate, final DividendFunctionProvider dividend) { ArgumentChecker.notNull(lattice, "lattice"); ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(dividend, "dividend"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); ArgumentChecker.isTrue(volatility > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); final LatticeSpecification modLattice = (lattice instanceof TimeVaryingLatticeSpecification) ? new TrigeorgisLatticeSpecification() : lattice; if (function instanceof BarrierOptionFunctionProvider) { final BarrierOptionFunctionProvider barrierFunction = (BarrierOptionFunctionProvider) function; if (barrierFunction.getChecker().checkOut(spot) || barrierFunction.getChecker().checkStrikeBehindBarrier()) { return 0.; } } final int nSteps = function.getNumberOfSteps(); final double strike = function.getStrike(); final double timeToExpiry = function.getTimeToExpiry(); final double dt = timeToExpiry / nSteps; ArgumentChecker.isTrue(dividend.checkTimeSteps(dt), "Number of steps is too small"); ArgumentChecker.isTrue(dividend.checkDividendBeforeExpiry(timeToExpiry), "Dividend is paid after expiry"); final double discount = Math.exp(-interestRate * dt); final double[] params = modLattice.getParameters(spot, strike, timeToExpiry, volatility, interestRate, nSteps, dt); final double upFactor = params[0]; final double downFactor = params[1]; final double upProbability = params[2]; final double downProbability = params[3]; final double upOverDown = upFactor / downFactor; ArgumentChecker.isTrue(upProbability > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability < 1., "upProbability should be smaller than 1."); final int[] divSteps = dividend.getDividendSteps(dt); double assetPriceBase = dividend.spotModifier(spot, interestRate); double[] values = function.getPayoffAtExpiry(assetPriceBase, downFactor, upOverDown); int counter = 0; final int nDivs = dividend.getNumberOfDividends(); if (dividend instanceof ProportionalDividendFunctionProvider) { for (int i = nSteps - 1; i > -1; --i) { for (int k = nDivs - 1 - counter; k > -1; --k) { if (i == divSteps[k]) { assetPriceBase = dividend.dividendCorrections(assetPriceBase, 0., 0., k); ++counter; } } values = function.getNextOptionValues(discount, upProbability, downProbability, values, assetPriceBase, 0., downFactor, upOverDown, i); } } else { double sumDiscountDiv = 0.; for (int i = nSteps - 1; i > -1; --i) { sumDiscountDiv *= Math.exp(-interestRate * dt); for (int k = nDivs - 1 - counter; k > -1; --k) { if (i == divSteps[k]) { sumDiscountDiv = dividend.dividendCorrections(sumDiscountDiv, interestRate, dt * i, k); ++counter; } } values = function.getNextOptionValues(discount, upProbability, downProbability, values, assetPriceBase, sumDiscountDiv, downFactor, upOverDown, i); } } return values[0]; } @Override public double getPrice(final OptionFunctionProvider2D function, final double spot1, final double spot2, final double volatility1, final double volatility2, final double correlation, final double interestRate, final double dividend1, final double dividend2) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.isTrue(spot1 > 0., "spot1 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot1), "spot1 should be finite"); ArgumentChecker.isTrue(spot2 > 0., "spot2 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot2), "spot2 should be finite"); ArgumentChecker.isTrue(volatility1 > 0., "volatility1 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility1), "volatility1 should be finite"); ArgumentChecker.isTrue(volatility2 > 0., "volatility2 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility2), "volatility2 should be finite"); ArgumentChecker.isTrue(correlation >= -1. && correlation <= 1., "correlation should be -1. <= rho <= 1."); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend1), "dividend1 should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend2), "dividend2 should be finite"); final int nSteps = function.getNumberOfSteps(); final double timeToExpiry = function.getTimeToExpiry(); final double vol12 = volatility1 * volatility2; final double vol11 = volatility1 * volatility1; final double vol22 = volatility2 * volatility2; final double dt = timeToExpiry / nSteps; final double discount = Math.exp(-interestRate * dt); final double rootDt = Math.sqrt(dt); final double dx1 = volatility1 * rootDt; final double dx2 = volatility2 * rootDt; final double dx12 = dx1 * dx2; final double nu1Factored = (interestRate - dividend1 - 0.5 * vol11) * dx2 * dt; final double nu2Factored = (interestRate - dividend2 - 0.5 * vol22) * dx1 * dt; final double vol12Factored = vol12 * correlation * dt; final double uuProbability = 0.25 * (dx12 + nu1Factored + nu2Factored + vol12Factored) / dx12; final double udProbability = 0.25 * (dx12 + nu1Factored - nu2Factored - vol12Factored) / dx12; final double duProbability = 0.25 * (dx12 - nu1Factored + nu2Factored - vol12Factored) / dx12; final double ddProbability = 0.25 * (dx12 - nu1Factored - nu2Factored + vol12Factored) / dx12; ArgumentChecker.isTrue(uuProbability > 0. && uuProbability < 1., "uuProbability should be 0 < p < 1."); ArgumentChecker.isTrue(udProbability > 0. && udProbability < 1., "udProbability should be 0 < p < 1."); ArgumentChecker.isTrue(duProbability > 0. && duProbability < 1., "duProbability should be 0 < p < 1."); ArgumentChecker.isTrue(ddProbability > 0. && ddProbability < 1., "ddProbability should be 0 < p < 1."); final double downFactor1 = Math.exp(-dx1); final double downFactor2 = Math.exp(-dx2); final double upOverDown1 = Math.exp(2. * dx1); final double upOverDown2 = Math.exp(2. * dx2); final double assetPrice1 = spot1 * Math.pow(downFactor1, nSteps); final double assetPrice2 = spot2 * Math.pow(downFactor2, nSteps); double[][] values = function.getPayoffAtExpiry(assetPrice1, assetPrice2, upOverDown1, upOverDown2); for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(discount, uuProbability, udProbability, duProbability, ddProbability, values, spot1, spot2, downFactor1, downFactor2, upOverDown1, upOverDown2, i); } return values[0][0]; } @Override public GreekResultCollection getGreeks(final LatticeSpecification lattice, final OptionFunctionProvider1D function, final double spot, final double volatility, final double interestRate, final double dividend) { ArgumentChecker.notNull(lattice, "lattice"); ArgumentChecker.notNull(function, "function"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); ArgumentChecker.isTrue(volatility > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend), "dividend should be finite"); final GreekResultCollection collection = new GreekResultCollection(); final LatticeSpecification modLattice = (lattice instanceof TimeVaryingLatticeSpecification) ? new TrigeorgisLatticeSpecification() : lattice; final int nSteps = function.getNumberOfSteps(); final double strike = function.getStrike(); final double timeToExpiry = function.getTimeToExpiry(); final double dt = timeToExpiry / nSteps; final double discount = Math.exp(-interestRate * dt); final double[] params = modLattice.getParameters(spot, strike, timeToExpiry, volatility, interestRate - dividend, nSteps, dt); final double upFactor = params[0]; final double downFactor = params[1]; final double upProbability = params[2]; final double downProbability = params[3]; final double upOverDown = upFactor / downFactor; ArgumentChecker.isTrue(upProbability > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability < 1., "upProbability should be smaller than 1."); double[] values = function.getPayoffAtExpiry(spot, downFactor, upOverDown); final double[] res = new double[4]; final double[] pForDelta = new double[] {spot * downFactor, spot * upFactor }; final double[] pForGamma = new double[] {pForDelta[0] * downFactor, pForDelta[0] * upFactor, pForDelta[1] * upFactor }; for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(discount, upProbability, downProbability, values, spot, 0., downFactor, upOverDown, i); if (i == 2) { res[2] = 2. * ((values[2] - values[1]) / (pForGamma[2] - pForGamma[1]) - (values[1] - values[0]) / (pForGamma[1] - pForGamma[0])) / (pForGamma[2] - pForGamma[0]); res[3] = values[1]; } if (i == 1) { res[1] = (values[1] - values[0]) / (pForDelta[1] - pForDelta[0]); } } res[0] = values[0]; res[3] = modLattice.getTheta(spot, volatility, interestRate, dividend, dt, res); collection.put(Greek.FAIR_PRICE, res[0]); collection.put(Greek.DELTA, res[1]); collection.put(Greek.GAMMA, res[2]); collection.put(Greek.THETA, res[3]); return collection; } /* * Array is used for dividend to realize constant cost of carry given by b = r - q */ @Override public GreekResultCollection getGreeks(final OptionFunctionProvider1D function, final double spot, final double[] volatility, final double[] interestRate, final double[] dividend) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(volatility, "volatility"); ArgumentChecker.notNull(interestRate, "interestRate"); ArgumentChecker.notNull(dividend, "dividend"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); final TimeVaryingLatticeSpecification vLattice = new TimeVaryingLatticeSpecification(); final int nSteps = function.getNumberOfSteps(); final double timeToExpiry = function.getTimeToExpiry(); ArgumentChecker.isTrue(nSteps == interestRate.length, "Wrong interestRate length"); ArgumentChecker.isTrue(nSteps == volatility.length, "Wrong volatility length"); ArgumentChecker.isTrue(nSteps == dividend.length, "Wrong dividend length"); for (int i = 0; i < nSteps; ++i) { ArgumentChecker.isTrue(volatility[i] > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility[i]), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate[i]), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend[i]), "dividend should be finite"); } final GreekResultCollection collection = new GreekResultCollection(); final double[] nu = vLattice.getShiftedDrift(volatility, interestRate, dividend); final double spaceStep = vLattice.getSpaceStep(timeToExpiry, volatility, nSteps, nu); final double upFactor = Math.exp(spaceStep); final double downFactor = Math.exp(-spaceStep); final double upOverDown = Math.exp(2. * spaceStep); final double[] upProbability = new double[nSteps]; final double[] downProbability = new double[nSteps]; final double[] df = new double[nSteps]; final double[] dt = new double[2]; for (int i = 0; i < nSteps; ++i) { final double[] params = vLattice.getParameters(volatility[i], nu[i], spaceStep); upProbability[i] = params[1]; downProbability[i] = 1. - params[1]; df[i] = Math.exp(-interestRate[i] * params[0]); if (i == 0) { dt[0] = params[0]; } if (i == 2) { dt[1] = params[1]; } ArgumentChecker.isTrue(upProbability[i] > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability[i] < 1., "upProbability should be smaller than 1."); } double[] values = function.getPayoffAtExpiry(spot, downFactor, upOverDown); final double[] res = new double[4]; final double[] pForDelta = new double[] {spot * downFactor, spot * upFactor }; final double[] pForGamma = new double[] {pForDelta[0] * downFactor, pForDelta[0] * upFactor, pForDelta[1] * upFactor }; for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(df[i], upProbability[i], downProbability[i], values, spot, 0., downFactor, upOverDown, i); if (i == 2) { res[2] = 2. * ((values[2] - values[1]) / (pForGamma[2] - pForGamma[1]) - (values[1] - values[0]) / (pForGamma[1] - pForGamma[0])) / (pForGamma[2] - pForGamma[0]); res[3] = values[1]; } if (i == 1) { res[1] = (values[1] - values[0]) / (pForDelta[1] - pForDelta[0]); } } res[0] = values[0]; res[3] = vLattice.getTheta(dt[0], dt[1], res); collection.put(Greek.FAIR_PRICE, res[0]); collection.put(Greek.DELTA, res[1]); collection.put(Greek.GAMMA, res[2]); collection.put(Greek.THETA, res[3]); return collection; } @Override public GreekResultCollection getGreeks(final LatticeSpecification lattice, final OptionFunctionProvider1D function, final double spot, final double volatility, final double interestRate, final DividendFunctionProvider dividend) { ArgumentChecker.notNull(lattice, "lattice"); ArgumentChecker.notNull(function, "function"); ArgumentChecker.notNull(dividend, "dividend"); ArgumentChecker.isTrue(spot > 0., "Spot should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot), "Spot should be finite"); ArgumentChecker.isTrue(volatility > 0., "volatility should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); final GreekResultCollection collection = new GreekResultCollection(); final LatticeSpecification modLattice = (lattice instanceof TimeVaryingLatticeSpecification) ? new TrigeorgisLatticeSpecification() : lattice; final int nSteps = function.getNumberOfSteps(); final double strike = function.getStrike(); final double timeToExpiry = function.getTimeToExpiry(); final double dt = timeToExpiry / nSteps; ArgumentChecker.isTrue(dividend.checkTimeSteps(dt), "Number of steps is too small"); ArgumentChecker.isTrue(dividend.checkDividendBeforeExpiry(timeToExpiry), "Dividend is paid after expiry"); final double discount = Math.exp(-interestRate * dt); final double[] params = modLattice.getParameters(spot, strike, timeToExpiry, volatility, interestRate, nSteps, dt); final double upFactor = params[0]; final double downFactor = params[1]; final double upProbability = params[2]; final double downProbability = params[3]; final double upOverDown = upFactor / downFactor; ArgumentChecker.isTrue(upProbability > 0., "upProbability should be greater than 0."); ArgumentChecker.isTrue(upProbability < 1., "upProbability should be smaller than 1."); final int[] divSteps = dividend.getDividendSteps(dt); double assetPriceBase = dividend.spotModifier(spot, interestRate); double[] values = function.getPayoffAtExpiry(assetPriceBase, downFactor, upOverDown); int counter = 0; final int nDivs = dividend.getNumberOfDividends(); final double[] res = new double[4]; if (dividend instanceof ProportionalDividendFunctionProvider) { for (int i = nSteps - 1; i > -1; --i) { for (int k = nDivs - 1 - counter; k > -1; --k) { if (i == divSteps[k]) { assetPriceBase = dividend.dividendCorrections(assetPriceBase, 0., 0., k); ++counter; } } values = function.getNextOptionValues(discount, upProbability, downProbability, values, assetPriceBase, 0., downFactor, upOverDown, i); if (i == 2) { final double[] pForGamma = dividend.getAssetPricesForGamma(spot, interestRate, divSteps, upFactor, downFactor, 0.); res[2] = 2. * ((values[2] - values[1]) / (pForGamma[2] - pForGamma[1]) - (values[1] - values[0]) / (pForGamma[1] - pForGamma[0])) / (pForGamma[2] - pForGamma[0]); res[3] = values[1]; } if (i == 1) { final double[] pForDelta = dividend.getAssetPricesForDelta(spot, interestRate, divSteps, upFactor, downFactor, 0.); res[1] = (values[1] - values[0]) / (pForDelta[1] - pForDelta[0]); } } } else { double sumDiscountDiv = 0.; for (int i = nSteps - 1; i > -1; --i) { sumDiscountDiv *= Math.exp(-interestRate * dt); for (int k = nDivs - 1 - counter; k > -1; --k) { if (i == divSteps[k]) { sumDiscountDiv = dividend.dividendCorrections(sumDiscountDiv, interestRate, dt * i, k); ++counter; } } values = function.getNextOptionValues(discount, upProbability, downProbability, values, assetPriceBase, sumDiscountDiv, downFactor, upOverDown, i); if (i == 2) { final double[] pForGamma = dividend.getAssetPricesForGamma(assetPriceBase, interestRate, divSteps, upFactor, downFactor, sumDiscountDiv); res[2] = 2. * ((values[2] - values[1]) / (pForGamma[2] - pForGamma[1]) - (values[1] - values[0]) / (pForGamma[1] - pForGamma[0])) / (pForGamma[2] - pForGamma[0]); res[3] = values[1]; } if (i == 1) { final double[] pForDelta = dividend.getAssetPricesForDelta(assetPriceBase, interestRate, divSteps, upFactor, downFactor, sumDiscountDiv); res[1] = (values[1] - values[0]) / (pForDelta[1] - pForDelta[0]); } } } res[0] = values[0]; res[3] = modLattice.getTheta(spot, volatility, interestRate, 0., dt, res); collection.put(Greek.FAIR_PRICE, res[0]); collection.put(Greek.DELTA, res[1]); collection.put(Greek.GAMMA, res[2]); collection.put(Greek.THETA, res[3]); return collection; } @Override public double[] getGreeks(final OptionFunctionProvider2D function, final double spot1, final double spot2, final double volatility1, final double volatility2, final double correlation, final double interestRate, final double dividend1, final double dividend2) { ArgumentChecker.notNull(function, "function"); ArgumentChecker.isTrue(spot1 > 0., "spot1 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot1), "spot1 should be finite"); ArgumentChecker.isTrue(spot2 > 0., "spot2 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(spot2), "spot2 should be finite"); ArgumentChecker.isTrue(volatility1 > 0., "volatility1 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility1), "volatility1 should be finite"); ArgumentChecker.isTrue(volatility2 > 0., "volatility2 should be positive"); ArgumentChecker.isTrue(Doubles.isFinite(volatility2), "volatility2 should be finite"); ArgumentChecker.isTrue(correlation >= -1. && correlation <= 1., "correlation should be -1. <= rho <= 1."); ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend1), "dividend1 should be finite"); ArgumentChecker.isTrue(Doubles.isFinite(dividend2), "dividend2 should be finite"); final int nSteps = function.getNumberOfSteps(); final double timeToExpiry = function.getTimeToExpiry(); final double[] res = new double[7]; final double vol12 = volatility1 * volatility2; final double vol11 = volatility1 * volatility1; final double vol22 = volatility2 * volatility2; final double dt = timeToExpiry / nSteps; final double discount = Math.exp(-interestRate * dt); final double rootDt = Math.sqrt(dt); final double dx1 = volatility1 * rootDt; final double dx2 = volatility2 * rootDt; final double dx12 = dx1 * dx2; final double nu1Factored = (interestRate - dividend1 - 0.5 * vol11) * dx2 * dt; final double nu2Factored = (interestRate - dividend2 - 0.5 * vol22) * dx1 * dt; final double vol12Factored = vol12 * correlation * dt; final double uuProbability = 0.25 * (dx12 + nu1Factored + nu2Factored + vol12Factored) / dx12; final double udProbability = 0.25 * (dx12 + nu1Factored - nu2Factored - vol12Factored) / dx12; final double duProbability = 0.25 * (dx12 - nu1Factored + nu2Factored - vol12Factored) / dx12; final double ddProbability = 0.25 * (dx12 - nu1Factored - nu2Factored + vol12Factored) / dx12; ArgumentChecker.isTrue(uuProbability > 0. && uuProbability < 1., "uuProbability should be 0 < p < 1."); ArgumentChecker.isTrue(udProbability > 0. && udProbability < 1., "udProbability should be 0 < p < 1."); ArgumentChecker.isTrue(duProbability > 0. && duProbability < 1., "duProbability should be 0 < p < 1."); ArgumentChecker.isTrue(ddProbability > 0. && ddProbability < 1., "ddProbability should be 0 < p < 1."); final double downFactor1 = Math.exp(-dx1); final double downFactor2 = Math.exp(-dx2); final double upOverDown1 = Math.exp(2. * dx1); final double upOverDown2 = Math.exp(2. * dx2); final double assetPrice1 = spot1 * Math.pow(downFactor1, nSteps); final double assetPrice2 = spot2 * Math.pow(downFactor2, nSteps); final double[] pForDelta1 = new double[] {spot1 * downFactor1, spot1 / downFactor1 }; final double[] pForDelta2 = new double[] {spot2 * downFactor2, spot2 / downFactor2 }; final double[] pForGamma1 = new double[] {pForDelta1[0] * downFactor1, spot1, pForDelta1[1] / downFactor1 }; final double[] pForGamma2 = new double[] {pForDelta2[0] * downFactor2, spot2, pForDelta2[1] / downFactor2 }; double[][] values = function.getPayoffAtExpiry(assetPrice1, assetPrice2, upOverDown1, upOverDown2); for (int i = nSteps - 1; i > -1; --i) { values = function.getNextOptionValues(discount, uuProbability, udProbability, duProbability, ddProbability, values, spot1, spot2, downFactor1, downFactor2, upOverDown1, upOverDown2, i); if (i == 2) { final double valDiff1huu = values[2][2] - values[1][2]; final double valDiff1luu = values[1][2] - values[0][2]; final double valDiff1hud = values[2][1] - values[1][1]; final double valDiff1lud = values[1][1] - values[0][1]; final double valDiff1hdd = values[2][0] - values[1][0]; final double valDiff1ldd = values[1][0] - values[0][0]; final double valDiff2huu = values[2][2] - values[2][1]; final double valDiff2luu = values[2][1] - values[2][0]; final double valDiff2hud = values[1][2] - values[1][1]; final double valDiff2lud = values[1][1] - values[1][0]; final double valDiff2hdd = values[0][2] - values[0][1]; final double valDiff2ldd = values[0][1] - values[0][0]; final double diff1h = pForGamma1[2] - pForGamma1[1]; final double diff1l = pForGamma1[1] - pForGamma1[0]; final double diff1 = pForGamma1[2] - pForGamma1[0]; final double diff2h = pForGamma2[2] - pForGamma2[1]; final double diff2l = pForGamma2[1] - pForGamma2[0]; final double diff2 = pForGamma2[2] - pForGamma2[0]; res[3] = values[1][1]; res[4] = 2. * ((valDiff1huu + valDiff1hud + valDiff1hdd) / diff1h - (valDiff1luu + valDiff1lud + valDiff1ldd) / diff1l) / diff1 / 3.; res[5] = 2. * ((valDiff2huu + valDiff2hud + valDiff2hdd) / diff2h - (valDiff2luu + valDiff2lud + valDiff2ldd) / diff2l) / diff2 / 3.; } if (i == 1) { final double muudu = values[1][1] - values[0][1]; final double muddd = values[1][0] - values[0][0]; final double muuud = values[1][1] - values[1][0]; final double mdudd = values[0][1] - values[0][0]; final double diff1 = pForDelta1[1] - pForDelta1[0]; final double diff2 = pForDelta2[1] - pForDelta2[0]; res[1] = 0.5 * (muudu + muddd) / diff1; res[2] = 0.5 * (muuud + mdudd) / diff2; res[6] = (muudu - muddd) / diff1 / diff2; } } res[3] = 0.5 * (res[3] - values[0][0]) / dt; res[0] = values[0][0]; return res; } @Override public double getPrice(final OptionFunctionProvider1D function, final StandardOptionDataBundle data) { throw new IllegalArgumentException("Not implemented"); } }