/**
* 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;
import org.apache.commons.lang.Validate;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.analytics.financial.greeks.AbstractGreekVisitor;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.option.definition.OptionDefinition;
import com.opengamma.analytics.financial.model.option.definition.StandardOptionDataBundle;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurface;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.surface.ConstantDoublesSurface;
import com.opengamma.util.time.DateUtils;
/**
* @param <S> Type of the option data bundle
* @param <T> Type of the option definition
*/
public class FiniteDifferenceGreekVisitor<S extends StandardOptionDataBundle, T extends OptionDefinition> extends AbstractGreekVisitor<Double> {
private static final double EPS = 1e-4; // TODO make this so it can be set
private final Function1D<S, Double> _pricingFunction;
private final S _data;
private final T _definition;
public FiniteDifferenceGreekVisitor(final Function1D<S, Double> pricingFunction, final S data, final T definition) {
Validate.notNull(pricingFunction, "pricing function");
Validate.notNull(data, "data");
Validate.notNull(definition, "definition");
_pricingFunction = pricingFunction;
_data = data;
_definition = definition;
}
@Override
public Double visitDelta() {
final Double s = _data.getSpot();
final S dataUp = (S) _data.withSpot(s + EPS);
final S dataDown = (S) _data.withSpot(s - EPS);
return getFirstDerivative(dataUp, dataDown);
}
@Override
public Double visitGamma() {
final Double s = _data.getSpot();
final S dataUp = (S) _data.withSpot(s + EPS);
final S dataDown = (S) _data.withSpot(s - EPS);
return getSecondDerivative(dataUp, dataDown, _data);
}
@Override
public Double visitVega() {
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getFirstDerivative(dataUp, dataDown);
}
@Override
public Double visitPrice() {
return _pricingFunction.evaluate(_data);
}
@Override
public Double visitRho() {
final ZonedDateTime date = _data.getDate();
final double t = _definition.getTimeToExpiry(date);
final double r = _data.getInterestRate(t);
final double b = _data.getCostOfCarry();
final YieldAndDiscountCurve upCurve = YieldCurve.from(ConstantDoublesCurve.from(r + EPS));
final YieldAndDiscountCurve downCurve = YieldCurve.from(ConstantDoublesCurve.from(r - EPS));
final S dataUp = (S) _data.withCostOfCarry(b + EPS).withInterestRateCurve(upCurve);
final S dataDown = (S) _data.withCostOfCarry(b - EPS).withInterestRateCurve(downCurve);
return getFirstDerivative(dataUp, dataDown);
}
@Override
public Double visitTheta() {
final ZonedDateTime date = _data.getDate();
final ZonedDateTime offset = DateUtils.getDateOffsetWithYearFraction(date, EPS);
final S dataUp = (S) _data.withDate(offset);
return getForwardFirstDerivative(dataUp, _data);
}
@Override
public Double visitCarryRho() {
final double b = _data.getCostOfCarry();
final S dataUp = (S) _data.withCostOfCarry(b + EPS);
final S dataDown = (S) _data.withCostOfCarry(b - EPS);
return getFirstDerivative(dataUp, dataDown);
}
@Override
public Double visitDZetaDVol() {
return null;
}
// TODO need to use forward differencing for dt?
@Override
public Double visitDeltaBleed() {
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final ZonedDateTime dateUp = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), EPS);
final ZonedDateTime dateDown = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), -EPS);
final S dataUp1Up2 = (S) _data.withSpot(sUp).withDate(dateUp);
final S dataUp1Down2 = (S) _data.withSpot(sUp).withDate(dateDown);
final S dataDown1Up2 = (S) _data.withSpot(sDown).withDate(dateUp);
final S dataDown1Down2 = (S) _data.withSpot(sDown).withDate(dateDown);
return getMixedSecondDerivative(dataUp1Up2, dataUp1Down2, dataDown1Up2, dataDown1Down2);
}
@Override
public Double visitDriftlessTheta() {
return null;
}
@Override
public Double visitElasticity() {
final double delta = visitDelta();
final double price = visitPrice();
return _data.getSpot() * delta / price;
}
@Override
public Double visitGammaBleed() {
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final ZonedDateTime dateUp = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), EPS);
final ZonedDateTime dateDown = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), -EPS);
final S dataUp1Up1 = (S) _data.withSpot(sUp).withDate(dateUp);
final S dataUp2 = (S) _data.withDate(dateUp);
final S dataDown1Up2 = (S) _data.withSpot(sDown).withDate(dateUp);
final S dataUp1Down2 = (S) _data.withSpot(sUp).withDate(dateDown);
final S dataDown2 = (S) _data.withDate(dateDown);
final S dataDown1Down2 = (S) _data.withSpot(sDown).withDate(dateDown);
return getMixedThirdDerivative(dataUp1Up1, dataUp2, dataDown1Up2, dataUp1Down2, dataDown2, dataDown1Down2);
}
@Override
public Double visitGammaP() {
return getGammaP(0, 0);
}
@Override
public Double visitGammaPBleed() {
final double gammaPUp = getGammaP(0, EPS);
final double gammaPDown = getGammaP(0, -EPS);
return (gammaPUp - gammaPDown) / (2 * EPS);
}
@Override
public Double visitPhi() {
return -visitCarryRho();
}
@Override
public Double visitSpeed() {
final double s = _data.getSpot();
final S dataUpUp = (S) _data.withSpot(s + 2 * EPS);
final S dataUp = (S) _data.withSpot(s + EPS);
final S dataDown = (S) _data.withSpot(s - EPS);
return getThirdDerivative(dataUpUp, dataUp, _data, dataDown);
}
@Override
public Double visitSpeedP() {
final double gammaPUp = getGammaP(EPS, 0);
final double gammaPDown = getGammaP(-EPS, 0);
return (gammaPUp - gammaPDown) / (2 * EPS);
}
@Override
public Double visitStrikeDelta() {
return null;
}
@Override
public Double visitStrikeGamma() {
return null;
}
@Override
public Double visitUltima() {
final VolatilitySurface upUpSurface = _data.getVolatilitySurface().withParallelShift(2 * EPS);
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUpUp = (S) _data.withVolatilitySurface(upUpSurface);
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getThirdDerivative(dataUpUp, dataUp, _data, dataDown);
}
@Override
public Double visitVanna() {
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp1Up2 = (S) _data.withSpot(sUp).withVolatilitySurface(upSurface);
final S dataUp1Down2 = (S) _data.withSpot(sDown).withVolatilitySurface(upSurface);
final S dataDown1Up2 = (S) _data.withSpot(sUp).withVolatilitySurface(downSurface);
final S dataDown1Down2 = (S) _data.withSpot(sDown).withVolatilitySurface(downSurface);
return getMixedSecondDerivative(dataUp1Up2, dataUp1Down2, dataDown1Up2, dataDown1Down2);
}
@Override
public Double visitVarianceUltima() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
final double variance = sigma * sigma;
final VolatilitySurface upUpSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance + 2 * EPS)));
final VolatilitySurface upSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance + EPS)));
final VolatilitySurface downSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance - EPS)));
final S dataUpUp = (S) _data.withVolatilitySurface(upUpSurface);
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getThirdDerivative(dataUpUp, dataUp, _data, dataDown);
}
@Override
public Double visitVarianceVanna() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
final double variance = sigma * sigma;
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final VolatilitySurface upSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance + EPS)));
final VolatilitySurface downSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance - EPS)));
final S dataUp1Up2 = (S) _data.withVolatilitySurface(upSurface).withSpot(sUp);
final S dataUp1Down2 = (S) _data.withVolatilitySurface(upSurface).withSpot(sDown);
final S dataDown1Up2 = (S) _data.withVolatilitySurface(downSurface).withSpot(sUp);
final S dataDown1Down2 = (S) _data.withVolatilitySurface(downSurface).withSpot(sDown);
return _data.getSpot() * getMixedSecondDerivative(dataUp1Up2, dataUp1Down2, dataDown1Up2, dataDown1Down2);
}
@Override
public Double visitVarianceVega() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
final double variance = sigma * sigma;
final VolatilitySurface upSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance + EPS)));
final VolatilitySurface downSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance - EPS)));
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getFirstDerivative(dataUp, dataDown);
}
@Override
public Double visitVarianceVomma() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
final double variance = sigma * sigma;
final VolatilitySurface upSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance + EPS)));
final VolatilitySurface downSurface = new VolatilitySurface(ConstantDoublesSurface.from(Math.sqrt(variance - EPS)));
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getSecondDerivative(dataUp, dataDown, _data);
}
@Override
public Double visitVegaBleed() {
final ZonedDateTime upDate = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), EPS);
final ZonedDateTime downDate = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), -EPS);
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp1Up2 = (S) _data.withVolatilitySurface(upSurface).withDate(upDate);
final S dataUp1Down2 = (S) _data.withVolatilitySurface(upSurface).withDate(downDate);
final S dataDown1Up2 = (S) _data.withVolatilitySurface(downSurface).withDate(upDate);
final S dataDown1Down2 = (S) _data.withVolatilitySurface(downSurface).withDate(downDate);
return getMixedSecondDerivative(dataUp1Up2, dataUp1Down2, dataDown1Up2, dataDown1Down2);
}
@Override
public Double visitVegaP() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
return visitVega() * sigma / 10;
}
@Override
public Double visitVomma() {
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp = (S) _data.withVolatilitySurface(upSurface);
final S dataDown = (S) _data.withVolatilitySurface(downSurface);
return getSecondDerivative(dataUp, dataDown, _data);
}
@Override
public Double visitVommaP() {
final double t = _definition.getTimeToExpiry(_data.getDate());
final double sigma = _data.getVolatility(t, _definition.getStrike());
return visitVomma() * sigma / 10;
}
@Override
public Double visitZeta() {
return null;
}
@Override
public Double visitZetaBleed() {
return null;
}
@Override
public Double visitZomma() {
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp1Up1 = (S) _data.withSpot(sUp).withVolatilitySurface(upSurface);
final S dataUp2 = (S) _data.withVolatilitySurface(upSurface);
final S dataDown1Up2 = (S) _data.withSpot(sDown).withVolatilitySurface(upSurface);
final S dataUp1Down2 = (S) _data.withSpot(sUp).withVolatilitySurface(downSurface);
final S dataDown2 = (S) _data.withVolatilitySurface(downSurface);
final S dataDown1Down2 = (S) _data.withSpot(sDown).withVolatilitySurface(downSurface);
return getMixedThirdDerivative(dataUp1Up1, dataUp2, dataDown1Up2, dataUp1Down2, dataDown2, dataDown1Down2);
}
@Override
public Double visitZommaP() {
return visitZomma() * _data.getSpot() / 100;
}
@Override
public Double visitDVannaDVol() {
final double s = _data.getSpot();
final double sUp = s + EPS;
final double sDown = s - EPS;
final VolatilitySurface upSurface = _data.getVolatilitySurface().withParallelShift(EPS);
final VolatilitySurface downSurface = _data.getVolatilitySurface().withParallelShift(-EPS);
final S dataUp1Up1 = (S) _data.withVolatilitySurface(upSurface).withSpot(sUp);
final S dataUp2 = (S) _data.withSpot(sUp);
final S dataDown1Up2 = (S) _data.withVolatilitySurface(downSurface).withSpot(sUp);
final S dataUp1Down2 = (S) _data.withVolatilitySurface(upSurface).withSpot(sDown);
final S dataDown2 = (S) _data.withSpot(sDown);
final S dataDown1Down2 = (S) _data.withVolatilitySurface(downSurface).withSpot(sDown);
return getMixedThirdDerivative(dataUp1Up1, dataUp2, dataDown1Up2, dataUp1Down2, dataDown2, dataDown1Down2);
}
private double getFirstDerivative(final S dataUp, final S dataDown) {
return (_pricingFunction.evaluate(dataUp) - _pricingFunction.evaluate(dataDown)) / (2 * EPS);
}
private double getForwardFirstDerivative(final S dataUp, final S data) {
return (_pricingFunction.evaluate(dataUp) - _pricingFunction.evaluate(data)) / EPS;
}
private double getSecondDerivative(final S dataUp, final S dataDown, final S data) {
return (_pricingFunction.evaluate(dataUp) + _pricingFunction.evaluate(dataDown) - 2 * _pricingFunction.evaluate(data)) / (EPS * EPS);
}
private double getMixedSecondDerivative(final S dataUp1Up2, final S dataUp1Down2, final S dataDown1Up2, final S dataDown1Down2) {
return (_pricingFunction.evaluate(dataUp1Up2) - _pricingFunction.evaluate(dataUp1Down2) - _pricingFunction.evaluate(dataDown1Up2) + _pricingFunction.evaluate(dataDown1Down2)) / (4 * EPS * EPS);
}
private double getThirdDerivative(final S dataUpUp, final S dataUp, final S data, final S dataDown) {
return (_pricingFunction.evaluate(dataUpUp) + 3 * _pricingFunction.evaluate(data) - 3 * _pricingFunction.evaluate(dataUp) - _pricingFunction.evaluate(dataDown)) / (EPS * EPS * EPS);
}
private double getMixedThirdDerivative(final S dataUp1Up1, final S dataUp2, final S dataDown1Up2, final S dataUp1Down2, final S dataDown2, final S dataDown1Down2) {
return (_pricingFunction.evaluate(dataUp1Up1) - 2 * _pricingFunction.evaluate(dataUp2) + _pricingFunction.evaluate(dataDown1Up2) - _pricingFunction.evaluate(dataUp1Down2) + 2
* _pricingFunction.evaluate(dataDown2) - _pricingFunction.evaluate(dataDown1Down2))
/ (2 * EPS * EPS * EPS);
}
private double getGammaP(final double spotOffset, final double tOffset) {
final double spot = _data.getSpot() + spotOffset;
final ZonedDateTime date = DateUtils.getDateOffsetWithYearFraction(_data.getDate(), tOffset);
final S dataUp = (S) _data.withSpot(spot + EPS).withDate(date);
final S dataDown = (S) _data.withSpot(spot - EPS).withDate(date);
final S data = (S) _data.withSpot(spot).withDate(date);
final double gamma = getSecondDerivative(dataUp, dataDown, data);
return gamma * spot / 100;
}
}