/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.equity.variance.pricing;
import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceConverter;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneyness;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.surface.PureImpliedVolatilitySurface;
import com.opengamma.analytics.math.FunctionUtils;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.integration.Integrator1D;
import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D;
/**
* Class to calculate the expected variance (NOT annualised) of an equity variance swap when a discount curve, affine dividends and either a PURE implied volatility surface or
* (classic) implied volatility surface is specified. See White (2012), Equity Variance Swap with Dividends, for details of the model
*/
public class EquityVarianceSwapStaticReplication {
/** The integrator */
private static final Integrator1D<Double, Double> INTEGRATOR = new RungeKuttaIntegrator1D();
/**
* Computes the computes the expected variance with and without adjustments for the dividend payments, by computing the price of a log-contract with expiry coinciding with that
* of the variance swap, and the value of the dividend corrections at all dividend dates, using the method of static replication from pure option prices obtained from the
* <b>pure</b> implied volatility surface (The pure< implied volatility is a number that put into Black formula (with unit forward) gives the price of puts and calls of the pure
* stock).
* @param spot The current level of the stock or index
* @param discountCurve The risk free interest rate curve
* @param dividends The dividends structure
* @param expiry The expiry of the variance swap
* @param volSurface A <b>pure</b> implied volatility surface -
* @return The expected variance (<b>not</b> annualised) with and without adjustments for the dividend payments (the former is usually the case for single stock and the latter for indices)
*/
public double[] expectedVariance(final double spot, final YieldAndDiscountCurve discountCurve, final AffineDividends dividends, final double expiry,
final PureImpliedVolatilitySurface volSurface) {
final EquityDividendsCurvesBundle divCurves = new EquityDividendsCurvesBundle(spot, discountCurve, dividends);
final double terminalFwd = divCurves.getF(expiry);
final double logNoDivFwd = Math.log(spot) + discountCurve.getInterestRate(expiry) * expiry;
final double logContract = integrate(getLogPayoffIntegrand(expiry, volSurface, divCurves)) + Math.log(terminalFwd);
double corrDivAdj = 0;
double uncorrDivAdj = 0;
int index = 0;
final int n = dividends.getNumberOfDividends();
while (n > 0 && index < n && dividends.getTau(index) <= expiry) {
final double f = divCurves.getF(dividends.getTau(index));
corrDivAdj += integrate(getCorrectedDividendAdjustmentIntegrand(index, volSurface, divCurves, dividends)) + getCorrectedDividendAdjustment(f, index, dividends);
uncorrDivAdj += integrate(getUncorrectedDividendAdjustmentIntegrand(index, volSurface, divCurves, dividends)) + getUncorrectedDividendAdjustment(f, index, dividends);
index++;
}
final double rvNoDivs = -2 * (logContract - logNoDivFwd);
final double rvCorrDivs = rvNoDivs + 2 * corrDivAdj;
final double rvUncorrDivs = rvNoDivs + 2 * uncorrDivAdj;
return new double[] {rvCorrDivs, rvUncorrDivs };
}
/**
* Computes the computes the expected variance with and without adjustments for the dividend payments, by computing the price of a log-contract with expiry coinciding with that
* of the variance swap, and the value of the dividend corrections at all dividend dates, using the method of static replication from option prices obtained from the
* implied volatility surface<p> <b>NOTE</b> For finite cash dividends, there is a arbitrage if the implied volatility remains smooth across the dividend date, hence simple interpolation
* from market option prices to form a (smooth) implied volatility surface WILL introduce arbitrage. It is therefore better to work with a pure implied volatility surface.
* @param spot The current level of the stock or index
* @param discountCurve The risk free interest rate curve
* @param dividends The dividends structure
* @param expiry The expiry of the variance swap
* @param volSurfaceStrike A implied volatility surface
* @return The expected variance (<b>not</b> annualised) with and without adjustments for the dividend payments (the former is usually the case for single stock and the latter for indices)
*/
public double[] expectedVariance(final double spot, final YieldAndDiscountCurve discountCurve, final AffineDividends dividends, final double expiry,
final BlackVolatilitySurfaceStrike volSurfaceStrike) {
final EquityDividendsCurvesBundle divCurves = new EquityDividendsCurvesBundle(spot, discountCurve, dividends);
final BlackVolatilitySurfaceMoneyness volSurface = BlackVolatilitySurfaceConverter.toMoneynessSurface(volSurfaceStrike, new ForwardCurve(divCurves.getF()));
final double terminalFwd = divCurves.getF(expiry);
final double logNoDivFwd = Math.log(spot) + discountCurve.getInterestRate(expiry) * expiry;
final double logContract = integrate(getLogPayoffIntegrand(expiry, volSurface)) + Math.log(terminalFwd);
double corrDivAdj = 0;
double uncorrDivAdj = 0;
int index = 0;
final int n = dividends.getNumberOfDividends();
while (n > 0 && index < n && dividends.getTau(index) <= expiry) {
final double f = divCurves.getF(dividends.getTau(index));
corrDivAdj += integrate(getCorrectedDividendAdjustmentIntegrand(index, volSurface, dividends)) + getCorrectedDividendAdjustment(f, index, dividends);
uncorrDivAdj += integrate(getUncorrectedDividendAdjustmentIntegrand(index, volSurface, dividends)) + getUncorrectedDividendAdjustment(f, index, dividends);
index++;
}
final double rvNoDivs = -2 * (logContract - logNoDivFwd);
final double rvCorrDivs = rvNoDivs + 2 * corrDivAdj;
final double rvUncorrDivs = rvNoDivs + 2 * uncorrDivAdj;
return new double[] {rvCorrDivs, rvUncorrDivs };
}
private double integrate(final Function1D<Double, Double> func) {
final double put = INTEGRATOR.integrate(func, 0.0, 1.0);
final double call = INTEGRATOR.integrate(func, 1.0, 50.0); //TODO set upper limit from tolerance
return put + call;
}
/**
* The (non-discounted) value of the log-payoff, $\mathbb{E}[S_T]$, can be computed as the log of the forward, $\log F_T$ plus the integral of this function from
* 0 to infinity (because of the non-smoothness when switching from puts at calls at x = 1, it is better to split the integral in two, 0 to 1 & 1 to infinity).
* @param expiry log-payoff expiry
* @param volSurface pure implied volatility surface
* @param divCurves dividend curves
* @return A function that integrates the log payoff
*/
private Function1D<Double, Double> getLogPayoffIntegrand(final double expiry, final PureImpliedVolatilitySurface volSurface, final EquityDividendsCurvesBundle divCurves) {
final double f = divCurves.getF(expiry);
final double d = divCurves.getD(expiry);
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0.0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatility(expiry, x);
final double price = BlackFormulaRepository.price(1.0, x, expiry, vol, isCall);
final double weight = -FunctionUtils.square((f - d) / ((f - d) * x + d));
return price * weight;
}
};
return integrand;
}
private Function1D<Double, Double> getLogPayoffIntegrand(final double expiry, final BlackVolatilitySurfaceMoneyness volSurface) {
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatilityForMoneyness(expiry, x);
final double otmPrice = BlackFormulaRepository.price(1.0, x, expiry, vol, isCall);
final double weight = -1 / x / x;
return otmPrice * weight;
}
};
return integrand;
}
private Function1D<Double, Double> getCorrectedDividendAdjustmentIntegrand(final int dividendIndex, final PureImpliedVolatilitySurface volSurface,
final EquityDividendsCurvesBundle divCurves, final AffineDividends dividends) {
final double tau = dividends.getTau(dividendIndex);
final double f = divCurves.getF(tau);
final double d = divCurves.getD(tau);
final double alpha = dividends.getAlpha(dividendIndex);
final double fMd2 = FunctionUtils.square(f - d);
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatility(tau, x);
final double otmPurePrice = BlackFormulaRepository.price(1.0, x, tau, vol, isCall);
final double s = (f - d) * x + d;
final double weight = -fMd2 * alpha * (2 * s + alpha) / s / s / FunctionUtils.square(s + alpha);
return otmPurePrice * weight;
}
};
return integrand;
}
private Function1D<Double, Double> getCorrectedDividendAdjustmentIntegrand(final int dividendIndex, final BlackVolatilitySurfaceMoneyness volSurface,
final AffineDividends dividends) {
final double tau = dividends.getTau(dividendIndex);
final double alpha = dividends.getAlpha(dividendIndex);
final double f = volSurface.getForwardCurve().getForward(tau);
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatilityForMoneyness(tau, x);
final double otmPrice = BlackFormulaRepository.price(1.0, x, tau, vol, isCall);
final double weight = -alpha * (2 * x * f + alpha) / x / x / FunctionUtils.square(x * f + alpha);
return otmPrice * weight;
}
};
return integrand;
}
private Function1D<Double, Double> getUncorrectedDividendAdjustmentIntegrand(final int dividendIndex, final PureImpliedVolatilitySurface volSurface,
final EquityDividendsCurvesBundle divCurves, final AffineDividends dividends) {
final double tau = dividends.getTau(dividendIndex);
final double f = divCurves.getF(tau);
final double d = divCurves.getD(tau);
final double alpha = dividends.getAlpha(dividendIndex);
final double beta = dividends.getBeta(dividendIndex);
final double fMd2 = FunctionUtils.square(f - d);
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatility(tau, x);
final double otmPurePrice = BlackFormulaRepository.price(1.0, x, tau, vol, isCall);
final double s = (f - d) * x + d;
final double sPalpha = s + alpha;
final double h = Math.log(s * (1 - beta) / sPalpha);
final double dH = alpha / s / sPalpha;
final double ddH = -alpha * (s + sPalpha) / s / s / sPalpha / sPalpha;
final double weight = fMd2 * ((1 + h) * ddH + dH * dH);
return otmPurePrice * weight;
}
};
return integrand;
}
private Function1D<Double, Double> getUncorrectedDividendAdjustmentIntegrand(final int dividendIndex, final BlackVolatilitySurfaceMoneyness volSurface,
final AffineDividends dividends) {
final double tau = dividends.getTau(dividendIndex);
final double alpha = dividends.getAlpha(dividendIndex);
final double beta = dividends.getBeta(dividendIndex);
final double f = volSurface.getForwardCurve().getForward(tau);
final Function1D<Double, Double> integrand = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
if (x == 0) {
return 0.0;
}
final boolean isCall = x >= 1.0;
final double vol = volSurface.getVolatilityForMoneyness(tau, x);
final double otmPrice = BlackFormulaRepository.price(1.0, x, tau, vol, isCall);
final double s = x * f;
final double sPalpha = s + alpha;
final double h = Math.log(s * (1 - beta) / sPalpha);
final double dH = alpha / x / sPalpha;
final double ddH = -alpha * (s + sPalpha) / x / x / sPalpha / sPalpha;
final double weight = ((1 + h) * ddH + dH * dH);
return otmPrice * weight;
}
};
return integrand;
}
private double getCorrectedDividendAdjustment(final double s, final int dividendIndex, final AffineDividends dividends) {
final double alpha = dividends.getAlpha(dividendIndex);
final double beta = dividends.getBeta(dividendIndex);
return Math.log(s * (1 - beta) / (s + alpha));
}
private double getUncorrectedDividendAdjustment(final double s, final int dividendIndex, final AffineDividends dividends) {
final double alpha = dividends.getAlpha(dividendIndex);
final double beta = dividends.getBeta(dividendIndex);
final double h = Math.log(s * (1 - beta) / (s + alpha));
return h + 0.5 * h * h;
}
}