/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.volatilityswap;
import java.util.Arrays;
import com.google.common.primitives.Doubles;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitorAdapter;
import com.opengamma.analytics.financial.model.volatility.surface.SmileDeltaTermStructureParameters;
import com.opengamma.analytics.financial.provider.description.volatilityswap.CarrLeeFXData;
import com.opengamma.analytics.math.FunctionUtils;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.rootfinding.NewtonRaphsonSingleRootFinder;
import com.opengamma.analytics.math.statistics.distribution.NormalDistribution;
import com.opengamma.analytics.math.statistics.distribution.ProbabilityDistribution;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Triple;
/**
*
*/
public class CarrLeeFXVolatilitySwapCalculator extends InstrumentDerivativeVisitorAdapter<CarrLeeFXData, VolatilitySwapCalculatorResult> {
private static final double DEFAULT_LOWEST_PUT_DELTA = -0.1;
private static final double DEFAULT_HIGHEST_CALL_DELTA = 0.1;
private static final int DEFAULT_NUM_POINTS = 50;
private static final ProbabilityDistribution<Double> NORMAL = new NormalDistribution(0, 1);
private static final CarrLeeNewlyIssuedSyntheticVolatilitySwapCalculator NEW_CALCULATOR = new CarrLeeNewlyIssuedSyntheticVolatilitySwapCalculator();
private static final CarrLeeSeasonedSyntheticVolatilitySwapCalculator SEASONED_CALCULATOR = new CarrLeeSeasonedSyntheticVolatilitySwapCalculator();
private final double _lowestPutDelta;
private final double _highestCallDelta;
private final int _numPoints;
private final double[] _strikeRange;
/**
* Default constructor
*/
public CarrLeeFXVolatilitySwapCalculator() {
this(DEFAULT_LOWEST_PUT_DELTA, DEFAULT_HIGHEST_CALL_DELTA, DEFAULT_NUM_POINTS);
}
/**
* Constructor specifying strike range and number of strikes
* @param lowestPutDelta The delta for put with lowest strike
* @param highestCallDelta The delta for call with highest strike
* @param numPoints The number of strikes between the lowest strike and the highest strike is (numPoints + 1)
*/
public CarrLeeFXVolatilitySwapCalculator(final double lowestPutDelta, final double highestCallDelta, final int numPoints) {
ArgumentChecker.isTrue(lowestPutDelta < 0. && lowestPutDelta > -1., "-1 < lowestPutDelta < 0 should be true");
ArgumentChecker.isTrue(highestCallDelta > 0. && highestCallDelta < 1., "0 < highestCallDelta < 1 should be true");
ArgumentChecker.isTrue(numPoints > 2, "numPoints should be greater than 2");
_lowestPutDelta = lowestPutDelta;
_highestCallDelta = highestCallDelta;
_numPoints = numPoints;
_strikeRange = null;
}
/**
* Constructor specifying number of strikes and strike range by strike values
* @param numPoints The number of strikes between the lowest strike and the highest strike is (numPoints + 1)
* @param strikeRange {minimum strike, maximum strike}
*/
public CarrLeeFXVolatilitySwapCalculator(final int numPoints, final double[] strikeRange) {
ArgumentChecker.isTrue(numPoints > 2, "numPoints should be greater than 2");
ArgumentChecker.notNull(strikeRange, "strikeRange");
ArgumentChecker.isTrue(strikeRange.length == 2, "length of strikeRange should be 2");
ArgumentChecker.isTrue(strikeRange[0] < strikeRange[1], "upper bound should be greater than lower bound");
_lowestPutDelta = 0.0;
_highestCallDelta = 0.0;
_numPoints = numPoints;
_strikeRange = Arrays.copyOf(strikeRange, 2);
}
@Override
public VolatilitySwapCalculatorResultWithStrikes visitFXVolatilitySwap(final FXVolatilitySwap swap, final CarrLeeFXData data) {
ArgumentChecker.notNull(swap, "swap");
ArgumentChecker.notNull(data, "data");
final double spot = data.getSpot();
final double timeToExpiry = swap.getTimeToMaturity();
ArgumentChecker.isTrue(Doubles.isFinite(timeToExpiry), "timeToExpiry should be finite");
ArgumentChecker.isTrue(timeToExpiry > 0., "timeToExpiry should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(spot), "spot should be finite");
ArgumentChecker.isTrue(spot > 0., "spot should be positive");
final double domesticDF = data.getMulticurveProvider().getDiscountFactor(swap.getBaseCurrency(), timeToExpiry);
final double foreignDF = data.getMulticurveProvider().getDiscountFactor(swap.getCounterCurrency(), timeToExpiry);
final double domesticRate = -Math.log(domesticDF) / timeToExpiry;
final double foreignRate = -Math.log(foreignDF) / timeToExpiry;
ArgumentChecker.isTrue(Doubles.isFinite(domesticRate), "domestic rate should be finite");
ArgumentChecker.isTrue(Doubles.isFinite(foreignRate), "foreign rate should be finite");
final double forward = spot * foreignDF / domesticDF;
final double timeFromInception = swap.getTimeToObservationStart() < 0 ? Math.abs(swap.getTimeToObservationStart()) : 0;
final double[] strikeRange;
if (_strikeRange == null) {
if (swap.getTimeToObservationStart() < 0) {
if (data.getRealizedVariance() == null) {
throw new IllegalStateException("Trying to price a seasoned swap but have null realized variance in the market data object");
}
final double reference = 3.0 * Math.sqrt(data.getRealizedVariance() * timeFromInception) / 100.;
strikeRange = getStrikeRange(timeToExpiry, data.getVolatilityData(), forward, reference);
} else {
strikeRange = getStrikeRange(timeToExpiry, data.getVolatilityData(), forward, 0.);
}
} else {
strikeRange = Arrays.copyOf(_strikeRange, 2);
ArgumentChecker.isTrue((forward > strikeRange[0] && forward < strikeRange[1]), "forward is outside of strike range");
}
final double deltaK = (strikeRange[1] - strikeRange[0]) / _numPoints;
final double[] strikes = new double[_numPoints + 1];
for (int i = 0; i < _numPoints; ++i) {
strikes[i] = strikeRange[0] + deltaK * i;
}
strikes[_numPoints] = strikeRange[1];
final int index = FunctionUtils.getLowerBoundIndex(strikes, forward);
final int nPuts = index + 1;
final int nCalls = _numPoints - index;
final double[] putStrikes = new double[nPuts];
final double[] callStrikes = new double[nCalls];
final double[] putVols = new double[nPuts];
final double[] callVols = new double[nCalls];
System.arraycopy(strikes, 0, putStrikes, 0, nPuts);
System.arraycopy(strikes, index + 1, callStrikes, 0, nCalls);
for (int i = 0; i < nPuts; ++i) {
putVols[i] = data.getVolatilityData().getVolatility(Triple.of(timeToExpiry, putStrikes[i], forward));
}
for (int i = 0; i < nCalls; ++i) {
callVols[i] = data.getVolatilityData().getVolatility(Triple.of(timeToExpiry, callStrikes[i], forward));
}
if (swap.getTimeToObservationStart() < 0) {
return (SEASONED_CALCULATOR.evaluate(spot, putStrikes, callStrikes, timeToExpiry, timeFromInception, domesticRate,
foreignRate, putVols, callVols, data.getRealizedVariance()).withStrikes(putStrikes, callStrikes));
}
final double strdVol = data.getVolatilityData().getVolatility(Triple.of(timeToExpiry, forward, forward));
return (NEW_CALCULATOR.evaluate(spot, putStrikes, callStrikes, timeToExpiry, domesticRate, foreignRate, putVols, strdVol, callVols)).withStrikes(putStrikes, callStrikes);
}
private double[] getStrikeRange(final double timeToExpiry, final SmileDeltaTermStructureParameters smile, final double forward, final double reference) {
final double[] res = new double[2];
res[0] = findStrike(_lowestPutDelta, timeToExpiry, smile, forward, false);
res[1] = findStrike(_highestCallDelta, timeToExpiry, smile, forward, true);
if (reference == 0.) {
return res;
}
res[0] = Math.min(res[0], forward * Math.exp(-reference));
res[1] = Math.max(res[1], forward * Math.exp(reference));
return res;
}
private double findStrike(final double delta, final double timeToExpiry, final SmileDeltaTermStructureParameters smile, final double forward, final boolean isCall) {
final Function1D<Double, Double> func = getDeltaDifference(delta, timeToExpiry, smile, forward, isCall);
final Function1D<Double, Double> funcDiff = getDeltaDifferenceDiff(timeToExpiry, smile, forward);
final NewtonRaphsonSingleRootFinder rtFinder = new NewtonRaphsonSingleRootFinder(1.e-12);
final double strike = rtFinder.getRoot(func, funcDiff, forward);
return strike;
}
private Function1D<Double, Double> getDeltaDifference(final double delta, final double timeToExpiry, final SmileDeltaTermStructureParameters smile, final double forward,
final boolean isCall) {
final double rootT = Math.sqrt(timeToExpiry);
return new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double strike) {
final double vol = smile.getVolatility(Triple.of(timeToExpiry, strike, forward));
final double sigRootT = vol * rootT;
final double d1 = Math.log(forward / strike) / sigRootT + 0.5 * sigRootT;
final double sign = isCall ? 1. : -1.;
return sign * NORMAL.getCDF(sign * d1) - delta;
}
};
}
private Function1D<Double, Double> getDeltaDifferenceDiff(final double timeToExpiry, final SmileDeltaTermStructureParameters smile, final double forward) {
final double rootT = Math.sqrt(timeToExpiry);
return new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double strike) {
final double vol = smile.getVolatility(Triple.of(timeToExpiry, strike, forward));
final double sigRootT = vol * rootT;
final double d1 = Math.log(forward / strike) / sigRootT + 0.5 * sigRootT;
return -NORMAL.getPDF(d1) / strike / sigRootT;
}
};
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(_highestCallDelta);
result = prime * result + (int) (temp ^ (temp >>> 32));
temp = Double.doubleToLongBits(_lowestPutDelta);
result = prime * result + (int) (temp ^ (temp >>> 32));
result = prime * result + _numPoints;
result = prime * result + Arrays.hashCode(_strikeRange);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof CarrLeeFXVolatilitySwapCalculator)) {
return false;
}
CarrLeeFXVolatilitySwapCalculator other = (CarrLeeFXVolatilitySwapCalculator) obj;
if (Double.doubleToLongBits(_highestCallDelta) != Double.doubleToLongBits(other._highestCallDelta)) {
return false;
}
if (Double.doubleToLongBits(_lowestPutDelta) != Double.doubleToLongBits(other._lowestPutDelta)) {
return false;
}
if (_numPoints != other._numPoints) {
return false;
}
if (!Arrays.equals(_strikeRange, other._strikeRange)) {
return false;
}
return true;
}
}