/**
* 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.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
/**
*
*/
public class BlackPriceFunction implements OptionPriceFunction<BlackFunctionData> {
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
@Override
public Function1D<BlackFunctionData, Double> getPriceFunction(final EuropeanVanillaOption option) {
Validate.notNull(option, "option");
final double k = option.getStrike();
final double t = option.getTimeToExpiry();
final boolean isCall = option.isCall();
return new Function1D<BlackFunctionData, Double>() {
@Override
public Double evaluate(final BlackFunctionData data) {
Validate.notNull(data, "data");
final double forward = data.getForward();
final double sigma = data.getBlackVolatility();
final double df = data.getDiscountFactor();
return df * BlackFormulaRepository.price(forward, k, t, sigma, isCall);
}
};
}
/**
* Return the Black price and its derivatives.
* @param option The option.
* @param data The Black data.
* @return An array with [0] the price, [1] the derivative with respect to the forward, [2] the derivative with respect to the volatility and
* [3] the derivative with respect to the strike.
*/
//TODO Refactor the method call to have the price as output and the derivatives as an array (like getPriceAdjoint2).
// TODO: [PLAT-6343]Add the derivative to the time to expiry (theta)
public double[] getPriceAdjoint(final EuropeanVanillaOption option, final BlackFunctionData data) {
/**
* The array storing the price and derivatives.
*/
double[] priceAdjoint = new double[4];
/**
* The cut-off for small time and strike.
*/
final double eps = 1E-16;
final double strike = option.getStrike();
final double timeToExpiry = option.getTimeToExpiry();
final double vol = data.getBlackVolatility();
final double forward = data.getForward();
final boolean isCall = option.isCall();
final double discountFactor = data.getDiscountFactor();
double sqrttheta = Math.sqrt(timeToExpiry);
double omega = isCall ? 1 : -1;
// Implementation Note: Forward sweep.
double volblack = 0, kappa = 0, d1 = 0, d2 = 0;
double x = 0;
if (strike < eps || sqrttheta < eps) {
x = omega * (forward - strike);
priceAdjoint[0] = (x > 0 ? discountFactor * x : 0.0);
} else {
volblack = vol * sqrttheta;
kappa = Math.log(forward / strike) / volblack - 0.5 * volblack;
d1 = NORMAL.getCDF(omega * (kappa + volblack));
d2 = NORMAL.getCDF(omega * kappa);
priceAdjoint[0] = discountFactor * omega * (forward * d1 - strike * d2);
}
// Implementation Note: Backward sweep.
double pBar = 1.0;
double forwardBar = 0, strikeBar = 0, volblackBar = 0, volatilityBar = 0;
if (strike < eps || sqrttheta < eps) {
forwardBar = (x > 0 ? discountFactor * omega : 0.0);
strikeBar = (x > 0 ? -discountFactor * omega : 0.0);
} else {
double d1Bar = discountFactor * omega * forward * pBar;
double density1 = NORMAL.getPDF(omega * (kappa + volblack));
// Implementation Note: kappa_bar = 0; no need to implement it.
// Methodology Note: kappa_bar is optimal exercise boundary. The
// derivative at the optimal point is 0.
forwardBar = discountFactor * omega * d1 * pBar;
strikeBar = -discountFactor * omega * d2 * pBar;
volblackBar = density1 * omega * d1Bar;
volatilityBar = sqrttheta * volblackBar;
}
priceAdjoint[1] = forwardBar;
priceAdjoint[2] = volatilityBar;
priceAdjoint[3] = strikeBar;
return priceAdjoint;
}
/**
* Return the Black price and its first and second order derivatives.
* @param option The option.
* @param data The Black data.
* @param bsD An array containing the price derivative [0] the derivative with respect to the forward, [1] the derivative with respect to the volatility and
* [2] the derivative with respect to the strike.
* @param bsD2 An array of array containing the price second order derivatives.
* Second order derivatives with respect to: [0][0] forward-forward [0][1] forward-volatility [0][2] forward-strike [1][1]volatility-volatility,
* [1][2] volatility-strike, [2][2] strike-strike
* @return The price.
*/
public double getPriceAdjoint2(final EuropeanVanillaOption option, final BlackFunctionData data, double[] bsD, double[][] bsD2) {
/**
* The cut-off for small time and strike.
*/
final double eps = 1E-16;
double p;
// Forward sweep
final double strike = option.getStrike();
final double timeToExpiry = option.getTimeToExpiry();
final double vol = data.getBlackVolatility();
final double forward = data.getForward();
final boolean isCall = option.isCall();
final double discountFactor = data.getDiscountFactor();
double sqrttheta = Math.sqrt(timeToExpiry);
double omega = isCall ? 1 : -1;
// Implementation Note: Forward sweep.
double volblack = 0, kappa = 0, d1 = 0, d2 = 0;
double x = 0;
if (strike < eps || sqrttheta < eps) {
x = omega * (forward - strike);
p = (x > 0 ? discountFactor * x : 0.0);
volblack = sqrttheta < eps ? 0 : (vol * sqrttheta);
} else {
volblack = vol * sqrttheta;
kappa = Math.log(forward / strike) / volblack - 0.5 * volblack;
d1 = NORMAL.getCDF(omega * (kappa + volblack));
d2 = NORMAL.getCDF(omega * kappa);
p = discountFactor * omega * (forward * d1 - strike * d2);
}
// Implementation Note: Backward sweep.
double pBar = 1.0;
double density1 = 0.0;
double d1Bar = 0.0;
double forwardBar = 0, strikeBar = 0, volblackBar = 0, volatilityBar = 0;
if (strike < eps || sqrttheta < eps) {
forwardBar = (x > 0 ? discountFactor * omega : 0.0);
strikeBar = (x > 0 ? -discountFactor * omega : 0.0);
} else {
d1Bar = discountFactor * omega * forward * pBar;
density1 = NORMAL.getPDF(omega * (kappa + volblack));
// Implementation Note: kappa_bar = 0; no need to implement it.
// Methodology Note: kappa_bar is optimal exercise boundary. The
// derivative at the optimal point is 0.
forwardBar = discountFactor * omega * d1 * pBar;
strikeBar = -discountFactor * omega * d2 * pBar;
volblackBar = density1 * omega * d1Bar;
volatilityBar = sqrttheta * volblackBar;
}
bsD[0] = forwardBar;
bsD[1] = volatilityBar;
bsD[2] = strikeBar;
if (strike < eps || sqrttheta < eps) {
return p;
}
// Backward sweep: second derivative
double d2Bar = -discountFactor * omega * strike;
double density2 = NORMAL.getPDF(omega * kappa);
double d1Kappa = omega * density1;
double d1KappaKappa = -(kappa + volblack) * d1Kappa;
double d2Kappa = omega * density2;
double d2KappaKappa = -kappa * d2Kappa;
double kappaKappaBar2 = d1KappaKappa * d1Bar + d2KappaKappa * d2Bar;
double kappaV = -Math.log(forward / strike) / (volblack * volblack) - 0.5;
double kappaVV = 2 * Math.log(forward / strike) / (volblack * volblack * volblack);
double d1TotVV = density1 * omega * (-(kappa + volblack) * (kappaV + 1) * (kappaV + 1) + kappaVV);
double d2TotVV = d2KappaKappa * kappaV * kappaV + d2Kappa * kappaVV;
double vVbar2 = d1Bar * d1TotVV + d2Bar * d2TotVV;
double volVolBar2 = vVbar2 * timeToExpiry;
double kappaStrikeBar2 = -discountFactor * omega * d2Kappa;
double kappaStrike = -1.0 / (strike * volblack);
double strikeStrikeBar2 = (kappaKappaBar2 * kappaStrike + 2 * kappaStrikeBar2) * kappaStrike;
double kappaStrikeV = 1.0 / strike / (volblack * volblack);
double d1VK = -omega * (kappa + volblack) * density1 * (kappaV + 1) * kappaStrike + omega * density1 * kappaStrikeV;
double d2V = d2Kappa * kappaV;
double d2VK = -omega * kappa * density2 * kappaV * kappaStrike + omega * density2 * kappaStrikeV;
double strikeD2Bar2 = -discountFactor * omega;
double strikeVolblackBar2 = strikeD2Bar2 * d2V + d1Bar * d1VK + d2Bar * d2VK;
double strikeVolBar2 = strikeVolblackBar2 * sqrttheta;
double kappaForward = 1.0 / (forward * volblack);
double forwardForwardBar2 = discountFactor * omega * d1Kappa * kappaForward;
double strikeForwardBar2 = discountFactor * omega * d1Kappa * kappaStrike;
double volForwardBar2 = discountFactor * omega * d1Kappa * (kappaV + 1) * sqrttheta;
bsD2[0][0] = forwardForwardBar2;
bsD2[0][1] = volForwardBar2;
bsD2[1][0] = volForwardBar2;
bsD2[0][2] = strikeForwardBar2;
bsD2[2][0] = strikeForwardBar2;
bsD2[1][1] = volVolBar2;
bsD2[1][2] = strikeVolBar2;
bsD2[2][1] = strikeVolBar2;
bsD2[2][2] = strikeStrikeBar2;
return p;
}
public Function1D<BlackFunctionData, Double> getVegaFunction(final EuropeanVanillaOption option) {
Validate.notNull(option, "option");
final double k = option.getStrike();
final double t = option.getTimeToExpiry();
return new Function1D<BlackFunctionData, Double>() {
@Override
public Double evaluate(final BlackFunctionData data) {
Validate.notNull(data, "data");
final double sigma = data.getBlackVolatility();
final double f = data.getForward();
final double discountFactor = data.getDiscountFactor();
return discountFactor * BlackFormulaRepository.vega(f, k, t, sigma);
}
};
}
}