/**
* 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.analytic.formula;
import org.apache.commons.lang.Validate;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
/**
* Computes the price of an option in the normally distributed assets hypothesis (Bachelier model).
*/
public class NormalPriceFunction implements OptionPriceFunction<NormalFunctionData> {
/**
* The normal distribution implementation.
*/
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
@Override
public Function1D<NormalFunctionData, Double> getPriceFunction(final EuropeanVanillaOption option) {
Validate.notNull(option, "option");
final double strike = option.getStrike();
final double t = option.getTimeToExpiry();
return new Function1D<NormalFunctionData, Double>() {
private static final long serialVersionUID = 1L;
@SuppressWarnings("synthetic-access")
@Override
public final Double evaluate(final NormalFunctionData data) {
Validate.notNull(data, "data");
final double forward = data.getForward();
final double numeraire = data.getNumeraire();
final double sigma = data.getNormalVolatility();
final double sigmaRootT = sigma * Math.sqrt(t);
final int sign = option.isCall() ? 1 : -1;
if (sigmaRootT < 1e-16) {
final double x = sign * (forward - strike);
return (x > 0 ? numeraire * x : 0.0);
}
final double arg = sign * (forward - strike) / sigmaRootT;
return numeraire * (sign * (forward - strike) * NORMAL.getCDF(arg) + sigmaRootT * NORMAL.getPDF(arg));
}
};
}
/**
* Computes the price of an option in the normally distributed assets hypothesis (Bachelier model). The first order price derivatives are also provided.
* @param option The option description.
* @param data The model data.
* @param priceDerivative Array used to output the derivative of the price with respect to [0] forward, [1] volatility, [2] strike. The length of the array should be 3.
* @return The price.
*/
public double getPriceAdjoint(final EuropeanVanillaOption option, final NormalFunctionData data, double[] priceDerivative) {
Validate.notNull(option, "option");
Validate.notNull(data, "data");
Validate.notNull(priceDerivative, "derivatives");
Validate.isTrue(priceDerivative.length == 3, "array size");
final double strike = option.getStrike();
final double t = option.getTimeToExpiry();
final double forward = data.getForward();
final double numeraire = data.getNumeraire();
final double sigma = data.getNormalVolatility();
final int sign = option.isCall() ? 1 : -1;
double price;
double nCDF = 0.0;
double nPDF = 0.0;
double arg = 0.0;
double x = 0.0;
// Implementation Note: Forward sweep.
final double sigmaRootT = sigma * Math.sqrt(t);
if (sigmaRootT < 1e-16) {
x = sign * (forward - strike);
price = (x > 0 ? numeraire * x : 0.0);
} else {
arg = sign * (forward - strike) / sigmaRootT;
nCDF = NORMAL.getCDF(arg);
nPDF = NORMAL.getPDF(arg);
price = numeraire * (sign * (forward - strike) * nCDF + sigmaRootT * nPDF);
}
// Implementation Note: Backward sweep.
double priceBar = 1.0;
if (sigmaRootT < 1e-16) {
double xBar = (x > 0 ? numeraire : 0.0);
priceDerivative[0] = sign * xBar;
priceDerivative[2] = -priceDerivative[0];
priceDerivative[1] = 0.0;
} else {
double nCDFBar = numeraire * (sign * (forward - strike)) * priceBar;
double nPDFBar = numeraire * sigmaRootT * priceBar;
double argBar = nPDF * nCDFBar - nPDF * arg * nPDFBar;
priceDerivative[0] = numeraire * sign * nCDF * priceBar + sign / sigmaRootT * argBar;
priceDerivative[2] = -priceDerivative[0];
double sigmaRootTBar = -arg / sigmaRootT * argBar + numeraire * nPDF * priceBar;
priceDerivative[1] = Math.sqrt(t) * sigmaRootTBar;
}
return price;
}
/**
* Computes forward delta of an option in the normally distributed assets hypothesis (Bachelier model).
* @param option The option description.
* @param data The model data.
* @return The Delta.
*/
public double getDelta(EuropeanVanillaOption option, NormalFunctionData data) {
Validate.notNull(option, "option");
Validate.notNull(data, "data");
double strike = option.getStrike();
double t = option.getTimeToExpiry();
double forward = data.getForward();
double numeraire = data.getNumeraire();
double sigma = data.getNormalVolatility();
int sign = option.isCall() ? 1 : -1;
double sigmaRootT = sigma * Math.sqrt(t);
if (sigmaRootT < 1e-16) {
double x = sign * (forward - strike);
if (Math.abs(x) <= 1e-16) {
return sign * 0.5 * numeraire; // ambiguous if x and sigmaRootT are tiny, then reference number is returned
}
return x > 0 ? sign * numeraire : 0.0;
}
double arg = sign * (forward - strike) / sigmaRootT;
double nCDF = NORMAL.getCDF(arg);
return numeraire * sign * nCDF;
}
/**
* Computes forward gamma of an option in the normally distributed assets hypothesis (Bachelier model).
* @param option The option description.
* @param data The model data.
* @return The gamma.
*/
public double getGamma(EuropeanVanillaOption option, NormalFunctionData data) {
Validate.notNull(option, "option");
Validate.notNull(data, "data");
double strike = option.getStrike();
double t = option.getTimeToExpiry();
double forward = data.getForward();
double numeraire = data.getNumeraire();
double sigma = data.getNormalVolatility();
int sign = option.isCall() ? 1 : -1;
double sigmaRootT = sigma * Math.sqrt(t);
if (sigmaRootT < 1e-16) {
double x = sign * (forward - strike);
// ambiguous (tend to be infinite) if x and sigmaRootT are tiny, then reference number is returned
return Math.abs(x) > 1e-16 ? 0.0 : numeraire / Math.sqrt(2.0 * Math.PI) / sigmaRootT;
}
double arg = (forward - strike) / sigmaRootT;
double nPDF = NORMAL.getPDF(arg);
return numeraire * nPDF / sigmaRootT;
}
/**
* Computes vega of an option in the normally distributed assets hypothesis (Bachelier model).
* @param option The option description.
* @param data The model data.
* @return The vega.
*/
public double getVega(EuropeanVanillaOption option, NormalFunctionData data) {
Validate.notNull(option, "option");
Validate.notNull(data, "data");
double strike = option.getStrike();
double t = option.getTimeToExpiry();
double forward = data.getForward();
double numeraire = data.getNumeraire();
double sigma = data.getNormalVolatility();
int sign = option.isCall() ? 1 : -1;
double rootT = Math.sqrt(t);
double sigmaRootT = sigma * rootT;
if (sigmaRootT < 1e-16) {
double x = sign * (forward - strike);
// ambiguous if x and sigmaRootT are tiny, then reference number is returned
return Math.abs(x) > 1e-16 ? 0.0 : numeraire * rootT / Math.sqrt(2.0 * Math.PI);
}
double arg = (forward - strike) / sigmaRootT;
double nPDF = NORMAL.getPDF(arg);
return numeraire * nPDF * rootT;
}
/**
* Computes theta of an option in the normally distributed assets hypothesis (Bachelier model).
* @param option The option description.
* @param data The model data.
* @return The theta.
*/
public double getTheta(EuropeanVanillaOption option, NormalFunctionData data) {
Validate.notNull(option, "option");
Validate.notNull(data, "data");
double strike = option.getStrike();
double t = option.getTimeToExpiry();
double forward = data.getForward();
double numeraire = data.getNumeraire();
double sigma = data.getNormalVolatility();
int sign = option.isCall() ? 1 : -1;
double rootT = Math.sqrt(t);
double sigmaRootT = sigma * rootT;
if (sigmaRootT < 1e-16) {
double x = sign * (forward - strike);
// ambiguous if x and sigmaRootT are tiny, then reference number is returned
return Math.abs(x) > 1e-16 ? 0.0 : -0.5 * numeraire * sigma / rootT / Math.sqrt(2.0 * Math.PI);
}
double arg = (forward - strike) / sigmaRootT;
double nPDF = NORMAL.getPDF(arg);
return -0.5 * numeraire * nPDF * sigma / rootT;
}
}