/**
* 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 java.util.Arrays;
import com.google.common.primitives.Doubles;
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.ArgumentChecker;
/**
*
*/
public class AmericanVanillaOptionFunctionProvider extends OptionFunctionProvider1D {
private final Calculator _calc;
/**
* @param strike Strike price
* @param timeToExpiry Time to expiry
* @param steps Number of steps
* @param isCall True if call, false if put
*/
public AmericanVanillaOptionFunctionProvider(final double strike, final double timeToExpiry, final int steps, final boolean isCall) {
super(strike, timeToExpiry, steps, isCall);
_calc = new NormalCalculator();
}
/**
* American vanilla option function with acceleration technique in binomial model
* @param strike Strike price
* @param timeToExpiry Time to expiry
* @param steps Number of steps
* @param isCall True if call, false if put
* @param volatility Volatility
* @param interestRate Interest rate
* @param dividend Dividend
*/
public AmericanVanillaOptionFunctionProvider(final double strike, final double timeToExpiry, final int steps, final boolean isCall, final double volatility, final double interestRate,
final double dividend) {
super(strike, timeToExpiry, steps, isCall);
_calc = new AccelerationCalculator(volatility, interestRate, dividend);
}
/**
* American vanilla option function with truncation technique in binomial model
* @param strike Strike price
* @param timeToExpiry Time to expiry
* @param steps Number of steps
* @param isCall True if call, false if put
* @param volatility Volatility
* @param interestRate Interest rate
* @param dividend Dividend
* @param stdDev Truncation parameter
*/
public AmericanVanillaOptionFunctionProvider(final double strike, final double timeToExpiry, final int steps, final boolean isCall, final double volatility, final double interestRate,
final double dividend, final double stdDev) {
super(strike, timeToExpiry, steps, isCall);
_calc = new TruncationCalculator(volatility, interestRate, dividend, stdDev);
}
/**
* American vanilla option function with truncation and acceleration techniques in binomial model
* @param strike Strike price
* @param timeToExpiry Time to expiry
* @param steps Number of steps
* @param isCall True if call, false if put
* @param volatility Volatility
* @param interestRate Interest rate
* @param dividend Dividend
* @param stdDev Truncation parameter
* @param acc True if acceleration is used
*/
public AmericanVanillaOptionFunctionProvider(final double strike, final double timeToExpiry, final int steps, final boolean isCall, final double volatility, final double interestRate,
final double dividend, final double stdDev, final boolean acc) {
super(strike, timeToExpiry, steps, isCall);
_calc = acc ? new AcceleratedTruncationCalculator(volatility, interestRate, dividend, stdDev) : new TruncationCalculator(volatility, interestRate, dividend, stdDev);
}
@Override
public double[] getPayoffAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) {
return _calc.payoffsAtExpiry(assetPrice, downFactor, upOverDown);
}
@Override
public double[] getNextOptionValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double baseAssetPrice, final double sumCashDiv,
final double downFactor, final double upOverDown, final int steps) {
return _calc.nextValues(discount, upProbability, downProbability, values, sumCashDiv, baseAssetPrice, downFactor, upOverDown, steps);
}
@Override
public double[] getPayoffAtExpiryTrinomial(double assetPrice, final double downFactor, double middleOverDown) {
final double strike = getStrike();
final int nSteps = getNumberOfSteps();
final int nNodes = 2 * getNumberOfSteps() + 1;
final double sign = getSign();
final double[] values = new double[nNodes];
double priceTmp = assetPrice * Math.pow(downFactor, nSteps);
for (int i = 0; i < nNodes; ++i) {
values[i] = Math.max(sign * (priceTmp - strike), 0.);
priceTmp *= middleOverDown;
}
return values;
}
@Override
public double[] getNextOptionValues(final double discount, final double upProbability, final double middleProbability, final double downProbability, final double[] values,
final double baseAssetPrice, final double sumCashDiv, final double downFactor, final double middleOverDown, final int steps) {
final double strike = getStrike();
final double sign = getSign();
final int nNodes = 2 * steps + 1;
final double[] res = new double[nNodes];
double assetPrice = baseAssetPrice * Math.pow(downFactor, steps);
for (int j = 0; j < nNodes; ++j) {
res[j] = Math.max(discount * (upProbability * values[j + 2] + middleProbability * values[j + 1] + downProbability * values[j]), sign * (assetPrice + sumCashDiv - strike));
assetPrice *= middleOverDown;
}
return res;
}
/**
*
*
*
* Private class defines calculation method
*
*
*
*/
private abstract class Calculator {
abstract double[] payoffsAtExpiry(final double assetPrice, final double downFactor, final double upOverDown);
public double[] nextValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double sumCashDiv, final double baseAssetPrice,
final double downFactor, final double upOverDown, final int steps) {
final double strike = getStrike();
final double sign = getSign();
final int nStepsP = steps + 1;
double assetPrice = baseAssetPrice * Math.pow(downFactor, steps);
final double[] res = new double[nStepsP];
for (int j = 0; j < nStepsP; ++j) {
res[j] = Math.max(discount * (upProbability * values[j + 1] + downProbability * values[j]), sign * (assetPrice + sumCashDiv - strike));
assetPrice *= upOverDown;
}
return res;
}
}
private class NormalCalculator extends Calculator {
@Override
public double[] payoffsAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) {
final int nSteps = getNumberOfSteps();
final int nStepsP = nSteps + 1;
final double strike = getStrike();
final double sign = getSign();
double assetPriceTerminal = assetPrice * Math.pow(downFactor, nSteps);
final double[] values = new double[nStepsP];
for (int i = 0; i < nStepsP; ++i) {
values[i] = Math.max(sign * (assetPriceTerminal - strike), 0.);
assetPriceTerminal *= upOverDown;
}
return values;
}
}
private class AccelerationCalculator extends Calculator {
private final double _volatility;
private final double _interestRate;
private final double _dividend;
public AccelerationCalculator(final double volatility, final double interestRate, final double dividend) {
ArgumentChecker.isTrue(volatility > 0., "volatility should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite");
_volatility = volatility;
_interestRate = interestRate;
_dividend = dividend;
}
@Override
public double[] payoffsAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) {
final double[] values = new double[getNumberOfSteps() + 1];
Arrays.fill(values, 0.);
return values;
}
@Override
public double[] nextValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double sumCashDiv, final double baseAssetPrice,
final double downFactor, final double upOverDown, final int steps) {
if (getNumberOfSteps() - 1 == steps) {
final double strike = getStrike();
final double sign = getSign();
final int nStepsP = steps + 1;
final double[] res = new double[nStepsP];
double assetPrice = baseAssetPrice * Math.pow(downFactor, steps);
final double volatility = _volatility;
final double dt = getTimeToExpiry() / getNumberOfSteps();
final double interestRate = _interestRate;
final double dividend = _dividend;
final boolean isCall = sign == 1.;
for (int j = 0; j < nStepsP; ++j) {
res[j] = BlackScholesFormulaRepository.price(assetPrice, strike, dt, volatility, interestRate, interestRate - dividend, isCall);
assetPrice *= upOverDown;
}
return res;
}
return super.nextValues(discount, upProbability, downProbability, values, sumCashDiv, baseAssetPrice, downFactor, upOverDown, steps);
}
}
private class TruncationCalculator extends Calculator {
private final double _volatility;
private final double _interestRate;
private final double _dividend;
private final double _stdDiv;
public TruncationCalculator(final double volatility, final double interestRate, final double dividend, final double stdDiv) {
ArgumentChecker.isTrue(volatility > 0., "volatility should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite");
ArgumentChecker.isTrue(stdDiv > 0., "stdDiv should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(stdDiv), "stdDiv should be finite");
_volatility = volatility;
_interestRate = interestRate;
_dividend = dividend;
_stdDiv = stdDiv;
}
@Override
public double[] payoffsAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) {
final int nSteps = getNumberOfSteps();
final int nStepsP = nSteps + 1;
final double strike = getStrike();
final double sign = getSign();
double assetPriceLowest = assetPrice * Math.pow(downFactor, nSteps);
final int ref = (int) (Math.log(strike / assetPriceLowest) / Math.log(upOverDown));
final double[] values = new double[nStepsP];
Arrays.fill(values, 0.);
double tmpValue = assetPriceLowest * Math.pow(upOverDown, ref - 3);
for (int i = 0; i < 6; ++i) {
values[ref - 3 + i] = Math.max(sign * (tmpValue - strike), 0.);
tmpValue *= upOverDown;
}
return values;
}
@Override
public double[] nextValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double sumCashDiv, final double baseAssetPrice,
final double downFactor, final double upOverDown, final int steps) {
final double strike = getStrike();
final double sign = getSign();
final int nStepsP = steps + 1;
final double assetPriceLowest = baseAssetPrice * Math.pow(downFactor, steps);
final double[] res = new double[nStepsP];
Arrays.fill(res, 0.);
final double time = getTimeToExpiry() - steps * getTimeToExpiry() / getNumberOfSteps();
final double part1 = Math.log(strike / assetPriceLowest) - (_interestRate - _dividend) * time;
final double part2 = _stdDiv * _volatility * Math.sqrt(time);
int jmax = (int) ((part1 + part2) / Math.log(upOverDown));
int jmin = (int) ((part1 - part2) / Math.log(upOverDown)) + 1;
jmax = jmax > steps ? steps : jmax;
jmin = jmin < 0 ? 0 : jmin;
final boolean isCall = sign == 1.;
double tmpValue = assetPriceLowest * Math.pow(upOverDown, jmin - 3);
if (jmin > 2) {
res[jmin - 3] = BlackScholesFormulaRepository.price(tmpValue, strike, time, _volatility, _interestRate, _interestRate - _dividend, isCall);
}
tmpValue *= upOverDown;
if (jmin > 1) {
res[jmin - 2] = BlackScholesFormulaRepository.price(tmpValue, strike, time, _volatility, _interestRate, _interestRate - _dividend, isCall);
}
tmpValue *= upOverDown;
if (jmin > 0) {
res[jmin - 1] = BlackScholesFormulaRepository.price(tmpValue, strike, time, _volatility, _interestRate, _interestRate - _dividend, isCall);
}
tmpValue *= upOverDown;
for (int j = jmin; j < jmax + 1; ++j) {
res[j] = Math.max(discount * (upProbability * values[j + 1] + downProbability * values[j]), sign * (tmpValue + sumCashDiv - strike));
tmpValue *= upOverDown;
}
if (jmax < steps) {
res[jmax + 1] = BlackScholesFormulaRepository.price(tmpValue, strike, time, _volatility, _interestRate, _interestRate - _dividend, isCall);
}
tmpValue *= upOverDown;
if (jmax < steps - 1) {
res[jmax + 2] = BlackScholesFormulaRepository.price(tmpValue, strike, time, _volatility, _interestRate, _interestRate - _dividend, isCall);
}
return res;
}
}
private class AcceleratedTruncationCalculator extends Calculator {
private final double _volatility;
private final double _interestRate;
private final double _dividend;
private final double _stdDiv;
private final double _dt;
private final ProbabilityDistribution<Double> _normal = new NormalDistribution(0, 1);
public AcceleratedTruncationCalculator(final double volatility, final double interestRate, final double dividend, final double stdDiv) {
ArgumentChecker.isTrue(volatility > 0., "volatility should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(volatility), "volatility should be finite");
ArgumentChecker.isTrue(stdDiv > 0., "stdDiv should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(stdDiv), "stdDiv should be finite");
_volatility = volatility;
_interestRate = interestRate;
_dividend = dividend;
_stdDiv = stdDiv;
_dt = getTimeToExpiry() / getNumberOfSteps();
}
@Override
public double[] payoffsAtExpiry(final double assetPrice, final double downFactor, final double upOverDown) {
final int nStepsP = getNumberOfSteps() + 1;
final double[] values = new double[nStepsP];
Arrays.fill(values, 0.);
return values;
}
@Override
public double[] nextValues(final double discount, final double upProbability, final double downProbability, final double[] values, final double sumCashDiv, final double baseAssetPrice,
final double downFactor, final double upOverDown, final int steps) {
final double strike = getStrike();
final double sign = getSign();
final int nStepsP = steps + 1;
final double assetPriceLowest = baseAssetPrice * Math.pow(downFactor, steps);
final double[] res = new double[nStepsP];
final double time = getTimeToExpiry() - steps * _dt;
final double part1 = Math.log(strike / assetPriceLowest) - (_interestRate - _dividend) * time;
final double part2 = _stdDiv * _volatility * Math.sqrt(time);
final double logFactorInv = 1. / Math.log(upOverDown);
int jmax = (int) ((part1 + part2) * logFactorInv);
int jmin = (int) ((part1 - part2) * logFactorInv) + 1;
jmax = Math.min(jmax, steps);
jmin = Math.max(jmin, 0);
if (jmax == steps && jmin == 0) {
double assetPrice = assetPriceLowest;
for (int j = 0; j < nStepsP; ++j) {
res[j] = Math.max(discount * (upProbability * values[j + 1] + downProbability * values[j]), sign * (assetPrice + sumCashDiv - strike));
assetPrice *= upOverDown;
}
return res;
}
Arrays.fill(res, 0.);
jmax = jmax < steps - 1 ? jmax + 2 : (jmax < steps ? jmax - 1 : jmax);
jmin = jmin > 1 ? jmin - 2 : (jmin > 0 ? jmin - 1 : jmin);
double tmpValue = assetPriceLowest * Math.pow(upOverDown, jmin);
for (int j = jmin; j < jmax + 1; ++j) {
if (getNumberOfSteps() - 1 == steps) {
res[j] = blackScholesPrice(tmpValue, strike, _dt, _volatility, _interestRate, _dividend, sign);
} else {
res[j] = Math.max(discount * (upProbability * values[j + 1] + downProbability * values[j]), sign * (tmpValue + sumCashDiv - strike));
}
tmpValue *= upOverDown;
}
return res;
}
private double blackScholesPrice(final double spot, final double strike, final double time, final double vol, final double interestRate, final double dividend, final double sign) {
final double factor1 = Math.exp(-dividend * time);
final double factor2 = Math.exp(-interestRate * time);
final double sigRootT = vol * Math.sqrt(time);
final double part = (Math.log(spot / strike) + (interestRate - dividend) * time) / sigRootT;
final double d1 = part + 0.5 * sigRootT;
final double d2 = part - 0.5 * sigRootT;
return sign * (spot * factor1 * _normal.getCDF(sign * d1) - factor2 * strike * _normal.getCDF(sign * d2));
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof AmericanVanillaOptionFunctionProvider)) {
return false;
}
return super.equals(obj);
}
}