/**
* 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 cern.jet.math.Bessel;
import com.google.common.primitives.Doubles;
import com.opengamma.analytics.financial.model.volatility.BlackScholesFormulaRepository;
import com.opengamma.util.ArgumentChecker;
/**
* "Realized Volatility and Variance: Options via Swaps",
* Peter Carr and Roger Lee, Oct. 26, 2007
*/
public class CarrLeeNewlyIssuedSyntheticVolatilitySwapCalculator {
private static final double EPS = 1.e-12;
/**
* The respective strikes should be sorted in ascending order
* @param spot The spot of underlying
* @param putStrikes Strikes of put options
* @param callStrikes Strikes of call options
* @param timeToExpiry Time to expiry
* @param interestRate Interest rate
* @param dividend The dividend
* @param putVols Volatilities of put options
* @param strdVol Volatility of straddle
* @param callVols Volatilities of call options
* @return {@link VolatilitySwapCalculatorResult}
*/
public VolatilitySwapCalculatorResult evaluate(final double spot, final double[] putStrikes, final double[] callStrikes, final double timeToExpiry, final double interestRate, final double dividend,
final double[] putVols, final double strdVol, final double[] callVols) {
ArgumentChecker.notNull(callStrikes, "callStrikes");
ArgumentChecker.notNull(putStrikes, "putStrikes");
ArgumentChecker.notNull(callVols, "callVols");
ArgumentChecker.notNull(putVols, "putVols");
final int nCalls = callStrikes.length;
final int nPuts = putStrikes.length;
ArgumentChecker.isTrue(callVols.length == nCalls, "callVols.length == callStrikes.length should hold");
ArgumentChecker.isTrue(putVols.length == nPuts, "putVols.length == putStrikes.length should hold");
ArgumentChecker.isTrue(Doubles.isFinite(spot), "spot should be finite");
ArgumentChecker.isTrue(spot > 0., "spot should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(timeToExpiry), "timeToExpiry should be finite");
ArgumentChecker.isTrue(timeToExpiry > 0., "timeToExpiry should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(interestRate), "interestRate should be finite");
ArgumentChecker.isTrue(Doubles.isFinite(dividend), "dividend should be finite");
ArgumentChecker.isTrue(Doubles.isFinite(strdVol), "strdVol should be finite");
ArgumentChecker.isTrue(strdVol > 0., "strdVol should be positive");
final double deltaK = (callStrikes[nCalls - 1] - putStrikes[0]) / (nCalls + nPuts - 1);
for (int i = 0; i < nCalls; ++i) {
ArgumentChecker.isTrue(Doubles.isFinite(callStrikes[i]), "callStrikes should be finite");
ArgumentChecker.isTrue(callStrikes[i] > 0., "callStrikes should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(callVols[i]), "callVols should be finite");
ArgumentChecker.isTrue(callVols[i] > 0., "callVols should be positive");
if (i < nCalls - 1) {
ArgumentChecker.isTrue(Math.abs(callStrikes[i + 1] - callStrikes[i] - deltaK) < EPS, "All of the strikes should be equally spaced");
}
}
for (int i = 0; i < nPuts; ++i) {
ArgumentChecker.isTrue(Doubles.isFinite(putStrikes[i]), "putStrikes should be finite");
ArgumentChecker.isTrue(putStrikes[i] > 0., "putStrikes should be positive");
ArgumentChecker.isTrue(Doubles.isFinite(putVols[i]), "putVols should be finite");
ArgumentChecker.isTrue(putVols[i] > 0., "putVols should be positive");
if (i < nPuts - 1) {
ArgumentChecker.isTrue(Math.abs(putStrikes[i + 1] - putStrikes[i] - deltaK) < EPS, "All of the strikes should be equally spaced");
}
}
final double rate = interestRate - dividend;
final double discount = Math.exp(-interestRate * timeToExpiry);
final double forward = spot * Math.exp(rate * timeToExpiry);
ArgumentChecker.isTrue((callStrikes[0] > forward && putStrikes[nPuts - 1] < forward), "Max(putStrikes) < forward < Min(callStrikes) should hold");
final double factor = 100. / Math.sqrt(timeToExpiry) * Math.sqrt(Math.PI * 0.5);
final double straddleWeight = factor / forward;
final double[] callWeights = getWeight(forward, callStrikes, deltaK, straddleWeight);
final double[] putWeights = getWeight(forward, putStrikes, deltaK, straddleWeight);
final double distance = callStrikes[0] + putStrikes[nPuts - 1] - 2. * forward;
if (distance < -EPS) {
callWeights[0] = getNearestWeight(forward, deltaK, callStrikes[0], straddleWeight);
} else if (distance > EPS) {
putWeights[nPuts - 1] = getNearestWeight(forward, deltaK, putStrikes[nPuts - 1], straddleWeight);
}
final double[] putPrices = new double[nPuts];
final double[] callPrices = new double[nCalls];
final double straddlePrice = BlackScholesFormulaRepository.price(spot, forward, timeToExpiry, strdVol, interestRate, rate, true) + BlackScholesFormulaRepository.price(spot, forward,
timeToExpiry, strdVol, interestRate, rate, false);
for (int i = 0; i < nCalls; ++i) {
callPrices[i] = BlackScholesFormulaRepository.price(spot, callStrikes[i], timeToExpiry, callVols[i], interestRate, rate, true);
}
for (int i = 0; i < nPuts; ++i) {
putPrices[i] = BlackScholesFormulaRepository.price(spot, putStrikes[i], timeToExpiry, putVols[i], interestRate, rate, false);
}
final double cash = getCashAmount(callStrikes[0], putStrikes[nPuts - 1], forward, discount, factor);
return new VolatilitySwapCalculatorResult(putWeights, straddleWeight, callWeights, putPrices, straddlePrice, callPrices, cash);
}
private double[] getWeight(final double forward, final double[] strikes, final double deltaK, final double strdWeight) {
final int nStrikes = strikes.length;
final double[] res = new double[nStrikes];
final double comFactor = 0.5 * strdWeight * deltaK / forward;
for (int i = 0; i < nStrikes; ++i) {
final double mLocal = 0.5 * Math.log(strikes[i] / forward);
res[i] = comFactor * Math.signum(mLocal) * Math.exp(-3. * mLocal) * (Bessel.i1(mLocal) - Bessel.i0(mLocal));
}
return res;
}
private double getNearestWeight(final double forward, final double deltaK, final double kStar, final double strdWeight) {
final double mVal1 = 0.5 * Math.log((kStar + 0.5 * deltaK) / forward);
final double mVal2 = 0.5 * Math.log((kStar - 0.5 * deltaK) / forward);
return strdWeight * (Math.signum(mVal1) * Math.exp(-mVal1) * Bessel.i0(mVal1) - Math.signum(mVal2) * Math.exp(-mVal2) * Bessel.i0(mVal2) - 2.);
}
private double getCashAmount(final double callStrike, final double putStrike, final double forward, final double discount, final double factor) {
final double m1 = Math.log(callStrike / forward);
final double m2 = Math.log(putStrike / forward);
final double first = (callStrike - forward) * (Math.abs(Math.exp(0.5 * m2) * m2 * (Bessel.i0(0.5 * m2) - Bessel.i1(0.5 * m2))) - Math.abs(putStrike / forward - 1.));
final double second = (forward - putStrike) * (Math.abs(Math.exp(0.5 * m1) * m1 * (Bessel.i0(0.5 * m1) - Bessel.i1(0.5 * m1))) - Math.abs(callStrike / forward - 1.));
return discount * factor * (first + second) / (callStrike - putStrike);
}
}