/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.volatility.smile.fitting.demo;
import java.util.Arrays;
import java.util.BitSet;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.BlackScholesFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.SABRModelFitter;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.SmileModelFitter;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.GeneralSmileInterpolator;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SmileInterpolatorSABR;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SmileInterpolatorSpline;
import com.opengamma.analytics.financial.model.volatility.smile.function.SABRFormulaData;
import com.opengamma.analytics.financial.model.volatility.smile.function.SABRHaganVolatilityFunction;
import com.opengamma.analytics.financial.model.volatility.smile.function.SmileModelData;
import com.opengamma.analytics.financial.model.volatility.smile.function.VolatilityFunctionProvider;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.DoubleQuadraticInterpolator1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.statistics.leastsquare.LeastSquareResultsWithTransform;
/**
* The demo test showing smile construction from a sparse option data set.
*/
public class EquityIndexOptionSmileAndPriceGreeksE2ETest {
private static double SHARES = 10.0;
private static final int DAYS_TO_MAT = 23;
private static final int HOURS_TO_MAT = 0;
private static final int MINS_TO_MAT = 0;
private static final double EXPIRY = (DAYS_TO_MAT + (HOURS_TO_MAT + MINS_TO_MAT / 60.) / 24.) / 365.;
private static final double FORWARD = 3360;
private static final double RATE = 0.204 * 0.01;
private static final double DIVIDEND = 0.333 * 0.01;
private static final double DF = Math.exp(-RATE * EXPIRY);
private static final double SPOT = FORWARD * Math.exp((DIVIDEND - RATE) * EXPIRY);
private static final double[] PUT_STRIKES = new double[] {3050, 3100, 3125, 3300, 3350, 3550 };
private static final double[] CALL_STRIKES = new double[] {3075, 3325, 3450, 3525 };
private static final double[] PUT_PRICES = new double[] {7.57640856221836, 10.4492292389527, 12.3810949922475,
43.5400250456128, 61.8776592568553, 197.744462599924 };
private static final double[] CALL_PRICES = new double[] {292.512814702669, 85.6868660157612, 26.1322435193456,
9.48267272813102 };
private static final double[] PUT_IV;
private static final double[] CALL_IV;
static {
int nPuts = PUT_STRIKES.length;
PUT_IV = new double[nPuts];
for (int i = 0; i < nPuts; i++) {
PUT_IV[i] = BlackFormulaRepository.impliedVolatility(PUT_PRICES[i] / DF, FORWARD, PUT_STRIKES[i], EXPIRY, false);
}
int nCalls = CALL_STRIKES.length;
CALL_IV = new double[nCalls];
for (int i = 0; i < nCalls; i++) {
CALL_IV[i] = BlackFormulaRepository.impliedVolatility(CALL_PRICES[i] / DF, FORWARD, CALL_STRIKES[i], EXPIRY,
true);
}
}
// (fractional) shifts to volatility smile
private static final double[] VOL_SHOCKS = new double[] {-0.1, -0.05, -0.01, 0.0, 0.01, 0.05, 0.1 };
private static VolatilityFunctionProvider<SABRFormulaData> SABR = new SABRHaganVolatilityFunction();
private static double[] CALL_ERRORS;
private static double[] PUT_ERRORS;
private static final double LOWER_STRIKE = 3000.;
private static final double UPPER_STRIKE = 3600.;
private static final double STRIKE_STEP = 25.;
private static final double[] DISPLAY_STRIKES;
static {
int n = (int) ((UPPER_STRIKE - LOWER_STRIKE) / STRIKE_STEP + 1);
DISPLAY_STRIKES = new double[n];
for (int i = 0; i < n; i++) {
DISPLAY_STRIKES[i] = LOWER_STRIKE + i * STRIKE_STEP;
}
DISPLAY_STRIKES[n - 1] = UPPER_STRIKE;
}
static {
CALL_ERRORS = new double[CALL_STRIKES.length];
Arrays.fill(CALL_ERRORS, 1e-3); // 10bps
PUT_ERRORS = new double[PUT_STRIKES.length];
Arrays.fill(PUT_ERRORS, 1e-3); // 10bps
}
/**
* Call options, global SABR.
*/
@Test(description = "Demo", enabled = false)
public void fitSabrSmileCall() {
BitSet fixed = new BitSet();
fixed.set(1); // beta is fixed
double atmVol = 0.19;
double beta = 0.5;
double rho = -0.7;
double nu = 1.8;
double alpha = atmVol * Math.pow(FORWARD, 1 - beta);
DoubleMatrix1D start = new DoubleMatrix1D(alpha, beta, rho, nu);
SmileModelFitter<SABRFormulaData> sabrFitter = new SABRModelFitter(FORWARD, CALL_STRIKES, EXPIRY, CALL_IV,
CALL_ERRORS, SABR);
Function1D<Double, Double> smile = fitSmile(sabrFitter, start, fixed);
printDetails(smile, DISPLAY_STRIKES, true);
// printDetailsWithShift(smile, DISPLAY_STRIKES, true, VOL_SHOCKS); // vol shocks
}
/**
* Put options, global SABR.
*/
@Test(description = "Demo", enabled = false)
public void fitSabrSmilePut() {
BitSet fixed = new BitSet();
fixed.set(1); // beta is fixed
double atmVol = 0.19;
double beta = 0.5;
double rho = -0.9;
double nu = 1.8;
double alpha = atmVol * Math.pow(FORWARD, 1 - beta);
DoubleMatrix1D start = new DoubleMatrix1D(alpha, beta, rho, nu);
SmileModelFitter<SABRFormulaData> sabrFitter = new SABRModelFitter(FORWARD, PUT_STRIKES, EXPIRY, PUT_IV,
PUT_ERRORS, SABR);
Function1D<Double, Double> smile = fitSmile(sabrFitter, start, fixed);
printDetails(smile, DISPLAY_STRIKES, false);
// printDetailsWithShift(smile, DISPLAY_STRIKES, false, VOL_SHOCKS); // vol shocks
}
/**
* Call options, local SABR.
*/
@Test(description = "Demo", enabled = false)
void sabrInterpolationCallTest() {
GeneralSmileInterpolator sabr_interpolator = new SmileInterpolatorSABR();
Function1D<Double, Double> smile = sabr_interpolator.getVolatilityFunction(FORWARD, CALL_STRIKES, EXPIRY, CALL_IV);
printDetails(smile, DISPLAY_STRIKES, true);
// printDetailsWithShift(smile, DISPLAY_STRIKES, true, VOL_SHOCKS); // vol shocks
}
/**
* Put options, local SABR.
*/
@Test(description = "Demo", enabled = false)
void sabrInterpolationPutTest() {
GeneralSmileInterpolator sabr_interpolator = new SmileInterpolatorSABR();
Function1D<Double, Double> smile = sabr_interpolator.getVolatilityFunction(FORWARD, PUT_STRIKES, EXPIRY, PUT_IV);
printDetails(smile, DISPLAY_STRIKES, false);
// printDetailsWithShift(smile, DISPLAY_STRIKES, false, VOL_SHOCKS); // vol shocks
}
/**
* Call options, spline, shifted lognormal with reduced gradient.
*/
@Test(description = "Demo", enabled = false)
void splineInterpolationCallTest() {
GeneralSmileInterpolator spline = new SmileInterpolatorSpline(new DoubleQuadraticInterpolator1D(), "Quiet");
Function1D<Double, Double> smile = spline.getVolatilityFunction(FORWARD, CALL_STRIKES, EXPIRY, CALL_IV);
printDetails(smile, DISPLAY_STRIKES, true);
// printDetailsWithShift(smile, DISPLAY_STRIKES, true, VOL_SHOCKS); // vol shocks
}
/**
* Put options, spline, shifted lognormal with reduced gradient.
*/
@Test(description = "Demo", enabled = false)
void splineInterpolationPutTest() {
GeneralSmileInterpolator spline = new SmileInterpolatorSpline(new DoubleQuadraticInterpolator1D(), "Quiet");
Function1D<Double, Double> smile = spline.getVolatilityFunction(FORWARD, PUT_STRIKES, EXPIRY, PUT_IV);
printDetails(smile, DISPLAY_STRIKES, false);
// printDetailsWithShift(smile, DISPLAY_STRIKES, false, VOL_SHOCKS); // vol shocks
}
/**
* Call options, spline, shifted lognormal with zero gradient.
*/
@Test(description = "Demo", enabled = false)
void splineInterpolationFlatCallTest() {
GeneralSmileInterpolator spline = new SmileInterpolatorSpline(new DoubleQuadraticInterpolator1D(), "Flat");
Function1D<Double, Double> smile = spline.getVolatilityFunction(FORWARD, CALL_STRIKES, EXPIRY, CALL_IV);
printDetails(smile, DISPLAY_STRIKES, true);
// printDetailsWithShift(smile, DISPLAY_STRIKES, true, VOL_SHOCKS); // vol shocks
}
/**
* Put options, spline, shifted lognormal with zero gradient.
*/
@Test(description = "Demo", enabled = false)
void splineInterpolationFlatPutTest() {
GeneralSmileInterpolator spline = new SmileInterpolatorSpline(new DoubleQuadraticInterpolator1D(), "Flat");
Function1D<Double, Double> smile = spline.getVolatilityFunction(FORWARD, PUT_STRIKES, EXPIRY, PUT_IV);
printDetails(smile, DISPLAY_STRIKES, false);
// printDetailsWithShift(smile, DISPLAY_STRIKES, false, VOL_SHOCKS); // vol shocks
}
/**
* Print strikes and implied vols for market available call options.
*/
@Test(description = "Demo", enabled = false)
void printCallStrikeVol() {
int n = CALL_STRIKES.length;
for (int i = 0; i < n; ++i) {
System.out.println(CALL_STRIKES[i] + "\t" + CALL_IV[i]);
}
}
/**
* Print strikes and implied vols for market available put options.
*/
@Test(description = "Demo", enabled = false)
void printPutStrikeVol() {
int n = PUT_STRIKES.length;
for (int i = 0; i < n; ++i) {
System.out.println(PUT_STRIKES[i] + "\t" + PUT_IV[i]);
}
}
private Function1D<Double, Double> toSmileFunction(final double fwd, final double expiry, final SmileModelData data,
final VolatilityFunctionProvider<? extends SmileModelData> volModel) {
return new Function1D<Double, Double>() {
@Override
public Double evaluate(Double k) {
boolean isCall = k >= fwd;
EuropeanVanillaOption option = new EuropeanVanillaOption(k, expiry, isCall);
Function1D<SmileModelData, Double> func = (Function1D<SmileModelData, Double>) volModel.getVolatilityFunction(
option, fwd);
return func.evaluate(data);
}
};
}
private Function1D<Double, Double> fitSmile(SmileModelFitter<? extends SmileModelData> fitter, DoubleMatrix1D start,
BitSet fixed) {
LeastSquareResultsWithTransform res = fitter.solve(start, fixed);
System.out.println("chi-Square: " + res.getChiSq());
VolatilityFunctionProvider<?> model = fitter.getModel();
SmileModelData data = fitter.toSmileModelData(res.getModelParameters());
return toSmileFunction(FORWARD, EXPIRY, data, model);
}
/**
* Print the smile, price, greeks
*/
private void printDetails(Function1D<Double, Double> smile, double[] strikes, boolean isCall) {
double facDiv = Math.exp(-DIVIDEND * EXPIRY);
if (isCall) {
System.out.println("Call Strike\tImplied Vol\tPrice\tDelta\tGamma\tVega\tTheta\tRho");
} else {
System.out.println("Put Strike\tImplied Vol\tPrice\tDelta\tGamma\tVega\tTheta\tRho");
}
for (int i = 0; i < strikes.length; i++) {
double k = strikes[i];
double vol = smile.evaluate(k);
double price = DF * BlackFormulaRepository.price(FORWARD, k, EXPIRY, vol, isCall);
double delta = BlackFormulaRepository.delta(FORWARD, k, EXPIRY, vol, isCall) * facDiv * 100.0;
double gamma = BlackFormulaRepository.gamma(FORWARD, k, EXPIRY, vol) * facDiv * facDiv / DF * SPOT;
double vega = DF * BlackFormulaRepository.vega(FORWARD, k, EXPIRY, vol) * SHARES / 100.0;
double theta = BlackScholesFormulaRepository.theta(SPOT, k, EXPIRY, vol, RATE, RATE - DIVIDEND, isCall) * SHARES /
365.0;
double rho = BlackScholesFormulaRepository.rho(SPOT, k, EXPIRY, vol, RATE, RATE - DIVIDEND, isCall) * SHARES /
10000.0;
System.out.println(k + "\t" + vol + "\t" + price + "\t" + delta + "\t" + gamma + "\t" + vega + "\t" + theta +
"\t" + rho);
}
}
/**
* Print the (shifted) smile, price, greeks with shift
*/
private void printDetailsWithShift(Function1D<Double, Double> smile, double[] strikes, boolean isCall, double[] shifts) {
int nShifts = shifts.length;
double facDiv = Math.exp(-DIVIDEND * EXPIRY);
for (int j = 0; j < nShifts; ++j) {
System.out.println();
System.out.println("Vol shift: " + shifts[j]);
if (isCall) {
System.out.println("Call Strike\tShifted Vol\tPrice\tDelta\tGamma\tVega\tTheta\tRho");
} else {
System.out.println("Put Strike\tShifted Vol\tPrice\tDelta\tGamma\tVega\tTheta\tRho");
}
for (int i = 0; i < strikes.length; i++) {
double k = strikes[i];
double vol = smile.evaluate(k) + shifts[j];
double price = DF * BlackFormulaRepository.price(FORWARD, k, EXPIRY, vol, isCall);
double delta = BlackFormulaRepository.delta(FORWARD, k, EXPIRY, vol, isCall) * facDiv * 100.0;
double gamma = BlackFormulaRepository.gamma(FORWARD, k, EXPIRY, vol) * facDiv * facDiv / DF * SPOT;
double vega = DF * BlackFormulaRepository.vega(FORWARD, k, EXPIRY, vol) * SHARES / 100.0;
double theta = BlackScholesFormulaRepository.theta(SPOT, k, EXPIRY, vol, RATE, RATE - DIVIDEND, isCall) *
SHARES / 365.0;
double rho = BlackScholesFormulaRepository.rho(SPOT, k, EXPIRY, vol, RATE, RATE - DIVIDEND, isCall) * SHARES /
10000.0;
System.out.println(k + "\t" + vol + "\t" + price + "\t" + delta + "\t" + gamma + "\t" + vega + "\t" + theta +
"\t" + rho);
}
}
}
/**
* Test below is for debugging
*/
@Test(enabled = false)
public void testApr() {
double time = (62. + (21. + 29. / 60.) / 24.0) / 365.;
double fwd = 3440.26;
double rate = 0.119 * 0.01;
double dividend = 1.875 * 0.01;
boolean isCall = true;
double price = 108.1036;
double strike = 3450.0;
double spot = fwd * Math.exp((dividend - rate) * time);
double[] dividends = new double[] {2.298, 0.745, 1.271, 3.867, 2.989 };
double totalDiv = 0.0;
for (int i = 0; i < dividends.length; ++i) {
totalDiv += dividends[i];
}
System.out.println(totalDiv);
System.out.println(Math.log(1.0 + totalDiv / spot) / time);
printGreeks(price, fwd, strike, time, rate, dividend, isCall);
}
private void printGreeks(double price, double fwd, double strike, double time, double rate, double dividend,
boolean isCall) {
double facDiv = Math.exp(-dividend * time);
double df = Math.exp(-rate * time);
double fwdPrice = price / df;
double impliedVol = BlackFormulaRepository.impliedVolatility(fwdPrice, fwd, strike, time, isCall);
System.out.println("impliedVol: " + "\t" + impliedVol);
double spot = fwd * Math.exp(-(rate - dividend) * time);
double delta = BlackFormulaRepository.delta(fwd, strike, time, impliedVol, isCall) * facDiv * 100.0;
System.out.println("delta: " + "\t" + delta);
double gamma = BlackFormulaRepository.gamma(fwd, strike, time, impliedVol) * facDiv * facDiv / df * spot;
System.out.println("gamma: " + "\t" + gamma);
double vega = BlackFormulaRepository.vega(fwd, strike, time, impliedVol) * df * SHARES / 100.0;
System.out.println("vega: " + "\t" + vega);
double theta = BlackScholesFormulaRepository.theta(spot, strike, time, impliedVol, rate, rate - dividend, isCall) *
SHARES / 365.0;
System.out.println("theta: " + "\t" + theta);
double rho = (BlackScholesFormulaRepository.rho(spot, strike, time, impliedVol, rate, rate - dividend, isCall)) *
SHARES / 10000.0;
System.out.println("rho: " + "\t" + rho);
}
}