/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.volatility.local;
import java.io.PrintStream;
import java.util.Arrays;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.finitedifference.applications.PDEUtilityTools;
import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve;
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.smile.fitting.sabr.ForexSmileDeltaSurfaceDataBundle;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.sabr.SmileSurfaceDataBundle;
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.surface.BlackVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneyness;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurfaceInterpolator;
import com.opengamma.analytics.math.function.Function;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.CombinedInterpolatorExtrapolator;
import com.opengamma.analytics.math.interpolation.DoubleQuadraticInterpolator1D;
import com.opengamma.analytics.math.interpolation.LinearExtrapolator1D;
import com.opengamma.analytics.math.rootfinding.BisectionSingleRootFinder;
import com.opengamma.analytics.math.rootfinding.BracketRoot;
import com.opengamma.analytics.math.surface.FunctionalDoublesSurface;
import com.opengamma.analytics.math.surface.Surface;
/**
*
*/
public class ForexLocalVolatilityTest {
private static final DoubleQuadraticInterpolator1D INTERPOLATOR_1D = new DoubleQuadraticInterpolator1D();
private static final CombinedInterpolatorExtrapolator EXTRAPOLATOR_1D = new CombinedInterpolatorExtrapolator(INTERPOLATOR_1D, new LinearExtrapolator1D(INTERPOLATOR_1D));
//Instrument used for Vega/Greeks Reports
private static final double EXAMPLE_EXPIRY = 0.5;
private static final double EXAMPLE_STRIKE = 1.4;
private static final double[] DELTAS = new double[] {0.15, 0.25 };
// private static final double[] FORWARDS = new double[] {1.3400578756875536, 1.340097783513583, 1.3401632874217568, 1.3402609422950176, 1.3407805380931572, 1.341476699363105,
// 1.3421440211105033, 1.3428419833962038, 1.3528497010863676, 1.35183878530766 };
private static final double[] FORWARDS;
// private static final double SPOT = 1.34;
// private static double DRIFT = 0.05;
@SuppressWarnings("unused")
private static final String[] TENORS = new String[] {"1W", "2W", "3W", "1M", "3M", "6M", "9M", "1Y", "5Y", "10Y" };
private static final double[] EXPIRIES = new double[] {7. / 365, 14 / 365., 21 / 365., 1 / 12., 3 / 12., 0.5, 0.75, 1, 5, 10 };
private static final double[] ATM = new double[] {0.17045, 0.1688, 0.167425, 0.1697, 0.1641, 0.1642, 0.1641, 0.1642, 0.138, 0.12515 };
private static final double[][] RR = new double[][] { {-0.0168, -0.02935, -0.039125, -0.047325, -0.058325, -0.06055, -0.0621, -0.063, -0.032775, -0.023925 },
{-0.012025, -0.02015, -0.026, -0.0314, -0.0377, -0.03905, -0.0396, -0.0402, -0.02085, -0.015175 } };
private static final double[][] BUTT = new double[][] { {0.00665, 0.00725, 0.00835, 0.009075, 0.013175, 0.01505, 0.01565, 0.0163, 0.009275, 0.007075, },
{0.002725, 0.00335, 0.0038, 0.004, 0.0056, 0.0061, 0.00615, 0.00635, 0.00385, 0.002575 } };
// private static final double[] ATM;
// private static final double[][] RR;
// private static final double[][] BUTT;
// private static final double[][] STRIKES;
// private static final double[][] VOLS;
private static final int N = EXPIRIES.length;
private static final SmileSurfaceDataBundle MARKET_DATA;
private static final VolatilitySurfaceInterpolator SURFACE_FITTER = new VolatilitySurfaceInterpolator(/*new SmileInterpolatorSpline()*/);
private static final LocalVolatilityPDEGreekCalculator CAL;
private static final DupireLocalVolatilityCalculator DUPIRE = new DupireLocalVolatilityCalculator();
static {
FORWARDS = new double[N];
Arrays.fill(FORWARDS, 10.0);
// ATM = new double[N];
// Arrays.fill(ATM, 0.11);
// RR = new double[2][N];
// BUTT = new double[2][N];
//
// STRIKES = new double[N][];
// VOLS = new double[N][];
// for (int i = 0; i < N; i++) {
// // FORWARDS[i] = SPOT * Math.exp(DRIFT * EXPIRIES[i]);
// final SmileDeltaParameter cal = new SmileDeltaParameter(EXPIRIES[i], ATM[i], DELTAS,
// new double[] {RR[0][i], RR[1][i] }, new double[] {BUTT[0][i], BUTT[1][i] });
// STRIKES[i] = cal.getStrike(FORWARDS[i]);
// VOLS[i] = cal.getVolatility();
// }
MARKET_DATA = new ForexSmileDeltaSurfaceDataBundle(FORWARDS, EXPIRIES, DELTAS, ATM, RR, BUTT, true, EXTRAPOLATOR_1D);
//MARKET_DATA = new StandardSmileSurfaceDataBundle(FORWARDS, EXPIRIES, STRIKES, VOLS, true, EXTRAPOLATOR_1D);
CAL = new LocalVolatilityPDEGreekCalculator(MARKET_DATA.getForwardCurve(), EXPIRIES, MARKET_DATA.getStrikes(), MARKET_DATA.getVolatilities(), true);
}
//For each expiry, print the expiry and the strikes and implied volatilities
@Test
(enabled = false)
public void printMarketData() {
final double[][] strikes = MARKET_DATA.getStrikes();
final double[][] vols = MARKET_DATA.getVolatilities();
for (int i = 0; i < N; i++) {
System.out.println(EXPIRIES[i]);
}
System.out.print("\n");
for (int i = 0; i < N; i++) {
final int m = strikes[i].length;
for (int j = 0; j < m; j++) {
System.out.print(strikes[i][j] + "\t");
}
System.out.print("\n");
}
System.out.print("\n");
for (int i = 0; i < N; i++) {
final int m = strikes[i].length;
for (int j = 0; j < m; j++) {
System.out.print(vols[i][j] + "\t");
}
System.out.print("\n");
}
}
//Fit the market data at each time slice and print the smiles and a functions of both strike and delta
@Test
(enabled = false)
public void fitMarketData() {
final double lambda = 0.05;
final Function1D<Double, Double>[] smiles = SURFACE_FITTER.getIndependentSmileFits(MARKET_DATA);
// double debug = smiles[4].evaluate(2.2);
System.out.println("Fitted smiles by strike");
System.out.print("\t");
for (int j = 0; j < 100; j++) {
final double m = 0.3 + j * 2.7 / 99.;
System.out.print(m + "\t");
}
System.out.print("\n");
for (int i = 0; i < N; i++) {
final double f = FORWARDS[i];
System.out.print(EXPIRIES[i] + "\t");
for (int j = 0; j < 100; j++) {
final double m = 0.3 + j * 2.7 / 99.;
final double k = m * f;
final double vol = smiles[i].evaluate(k);
System.out.print(vol + "\t");
}
System.out.print("\n");
}
System.out.print("\n");
// System.out.println("Fitted smiles by proxy delta");
// System.out.print("\t");
// for (int j = 0; j < 100; j++) {
// final double d = -2.5 + j * 5.0 / 99.;
// System.out.print(d + "\t");
// }
// System.out.print("\n");
// for (int i = 0; i < N; i++) {
// double f = FORWARDS[i];
// double rootT = Math.sqrt(EXPIRIES[i] + lambda);
// System.out.print(EXPIRIES[i] + "\t");
// for (int j = 0; j < 100; j++) {
// final double d = -2.5 + j * 5.0 / 99.;
// double k = Math.exp(d * rootT) * f;
// final double vol = smiles[i].evaluate(k);
// System.out.print(vol + "\t");
// }
// System.out.print("\n");
// }
// System.out.print("\n");
// System.out.println("Fitted smiles by delta");
// System.out.print("\t");
// for (int j = 0; j < 100; j++) {
// final double d = 0.001 + j * 0.998 / 99.;
// System.out.print(d + "\t");
// }
// System.out.print("\n");
// for (int i = 0; i < N; i++) {
// System.out.print(EXPIRIES[i] + "\t");
// for (int j = 0; j < 100; j++) {
// final double d = 0.05 + j * 0.9 / 99.;
// final Function1D<Double, Double> func = getStrikeForDeltaFunction(FORWARDS[i], EXPIRIES[i], true, smiles[i]);
// final double vol = smiles[i].evaluate(func.evaluate(d));
// System.out.print(vol + "\t");
// }
// System.out.print("\n");
// }
}
/**
* Print the fitted implied vol surface and the derived implied vol
*/
@Test
(enabled = false)
public void printSurface() {
final BlackVolatilitySurfaceMoneyness surface = SURFACE_FITTER.getVolatilitySurface(MARKET_DATA);
PDEUtilityTools.printSurface("vol surface", surface.getSurface(), 0, 7. / 365, 0.95, 1.05, 200, 100);
// Surface<Double, Double, Double> dens = DUPIRE.getDensity(surface);
// PDEUtilityTools.printSurface("density surface", dens, 0.01, 1.0, 0.75, 1.25, 200, 100);
// Surface<Double, Double, Double> theta = DUPIRE.getTheta(surface);
// PDEUtilityTools.printSurface("theta surface", theta, 0, 10, 0.1, 3.0, 200, 100);
LocalVolatilitySurfaceMoneyness lv = DUPIRE.getLocalVolatility(surface);
PDEUtilityTools.printSurface("LV surface", lv.getSurface(), 0, 7 / 365., 0.95, 1.05, 200, 100);
// final LocalVolatilitySurfaceMoneyness lv2 = DUPIRE.getLocalVolatility(surface);
//
// PDEUtilityTools.printSurface("LV surface2", lv2.getSurface(), 0, 10, 0.3, 3.0, 200, 100);
}
@Test(enabled = false)
public void densityCheck() {
final double sigma = 0.25;
final Function<Double, Double> densityFunc = new Function<Double, Double>() {
@Override
public Double evaluate(final Double... x) {
final double t = x[0];
final double f = x[1];
return BlackFormulaRepository.dualGamma(1.0, f, t, sigma);
}
};
final FunctionalDoublesSurface density = FunctionalDoublesSurface.from(densityFunc);
PDEUtilityTools.printSurface("density surface", density, 0.01, 10, 0.0, 4.0, 200, 100);
}
@Test(enabled = false)
public void densityCheck2() {
final double alpha = 0.2;
final double beta = 0.7;
final double rho = -0.4;
final double nu = 0.3;
final SABRFormulaData data = new SABRFormulaData(alpha, beta, rho, nu);
final SABRHaganVolatilityFunction sabr = new SABRHaganVolatilityFunction();
final Function<Double, Double> densityFunc = new Function<Double, Double>() {
@Override
public Double evaluate(final Double... x) {
final double t = x[0];
final double k = x[1];
final double[] d1 = new double[5];
final double[][] d2 = new double[2][2];
final double vol = sabr.getVolatilityAdjoint2(new EuropeanVanillaOption(k, t, false), 1.0, data, d1, d2);
final double dSigmadK = d1[1];
final double d2SigmadK2 = d2[1][1];
final double dg = BlackFormulaRepository.dualGamma(1.0, k, t, vol);
final double vanna = BlackFormulaRepository.dualVanna(1.0, k, t, vol);
final double vega = BlackFormulaRepository.vega(1.0, k, t, vol);
final double vomma = BlackFormulaRepository.vomma(1.0, k, t, vol);
final double dens = dg + 2 * vanna * dSigmadK + vomma * dSigmadK * dSigmadK + vega * d2SigmadK2;
return dens;
}
};
final FunctionalDoublesSurface density = FunctionalDoublesSurface.from(densityFunc);
PDEUtilityTools.printSurface("density surface", density, 0.01, 10, 0.01, 5.0, 200, 100);
}
/**
* Print the fitted implied vol surface and the derived implied vol as a function of moneyness m = log(k/f)/(1+lambda*sqrt(t))
*/
@Test
(enabled = false)
public void printDeltaProxySurface() {
final double xMin = -0.5;
final double xMax = 0.5;
final BlackVolatilitySurfaceMoneyness surface = SURFACE_FITTER.getVolatilitySurface(MARKET_DATA);
final Surface<Double, Double, Double> moneynessSurface = toDeltaProxySurface(surface);
PDEUtilityTools.printSurface("moneyness surface", moneynessSurface, 0, 10, xMin, xMax, 200, 100);
// final LocalVolatilitySurfaceMoneyness lv = DUPIRE.getLocalVolatility(surface);
// final Surface<Double, Double, Double> lvMoneynessSurface = toDeltaProxySurface(lv);
// PDEUtilityTools.printSurface("LV moneyness surface", lvMoneynessSurface, 0.0, 10, xMin, xMax, 200, 100);
}
@Test
(enabled = false)
public void printPrices() {
final PrintStream ps = System.out;
CAL.prices(ps, EXAMPLE_EXPIRY, new double[] {0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8 });
}
@Test
(enabled = false)
public void runPDESolver() {
final long startTime = System.nanoTime();
final PrintStream ps = System.out;
CAL.runPDESolver(ps);
final long endTime = System.nanoTime();
ps.println("run time: " + (endTime - startTime) / 1e9 + "s");
}
@Test
(enabled = false)
public void runBackwardsPDESolver() {
final PrintStream ps = System.out;
CAL.runBackwardsPDESolver(ps, EXAMPLE_EXPIRY, EXAMPLE_STRIKE);
}
@Test
(enabled = false)
public void bucketedVega() {
final PrintStream ps = System.out;
final EuropeanVanillaOption option = new EuropeanVanillaOption(EXAMPLE_STRIKE, EXAMPLE_EXPIRY, true);
CAL.bucketedVegaForwardPDE(ps, option);
CAL.bucketedVegaBackwardsPDE(ps, option);
}
@Test
(enabled = false)
public void deltaAndGamma() {
final PrintStream ps = System.out;
CAL.deltaAndGamma(ps, EXAMPLE_EXPIRY, EXAMPLE_STRIKE);
}
/**
* uses a pathological local volatility surface to debug problems with forward pde greek calculations
*/
@Test
(enabled = false)
public void debugDeltaAndGamma() {
final PrintStream ps = System.out;
final double lVol = 0.20;
final double hVol = 0.3;
final Function<Double, Double> lvFunc = new Function<Double, Double>() {
double a = (lVol + hVol) / 2.0;
double b = (hVol - lVol) / 2.0;
double width = 0.02;
double lambda = Math.PI / 2 / width;
@Override
public Double evaluate(final Double... x) {
final double t = x[0];
final double k = x[1];
final double d = Math.abs(Math.log(k / FORWARDS[0])) / Math.sqrt(0.1 + t);
if (d < (0.1 - width)) {
return hVol;
} else if (d < (0.1 + width)) {
return a + b * Math.sin(-(d - 0.1) * lambda);
} else if (d > (0.3 + width)) {
return hVol;
} else if (d > (0.3 - width)) {
return a + b * Math.sin((d - 0.3) * lambda);
} else {
return lVol;
}
}
};
final LocalVolatilitySurfaceStrike lv = new LocalVolatilitySurfaceStrike(FunctionalDoublesSurface.from(lvFunc));
PDEUtilityTools.printSurface("LV surface", lv.getSurface(), 0, 10, 0.3, 3.0, 200, 100);
CAL.deltaAndGamma(ps, EXAMPLE_EXPIRY, FORWARDS[0], lv);
}
@Test
(enabled = false)
public void smileDynamic() {
final PrintStream ps = System.out;
CAL.smileDynamic(ps, EXAMPLE_EXPIRY, 0.1);
}
/**
* print out vega based greeks
*/
@Test
(enabled = false)
public void vega() {
final PrintStream ps = System.out;
final EuropeanVanillaOption option = new EuropeanVanillaOption(EXAMPLE_STRIKE, EXAMPLE_EXPIRY, true);
CAL.vega(ps, option);
}
@SuppressWarnings("unused")
private Function1D<Double, Double> getStrikeForDeltaFunction(final double forward, final double expiry, final boolean isCall,
final Function1D<Double, Double> volFunc) {
final BracketRoot bracketer = new BracketRoot();
final BisectionSingleRootFinder rootFinder = new BisectionSingleRootFinder(1e-8);
return new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double delta) {
final Function1D<Double, Double> deltaFunc = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double strike) {
final double vol = volFunc.evaluate(strike);
final double deltaTry = BlackFormulaRepository.delta(forward, strike, expiry, vol, isCall);
return deltaTry - delta;
}
};
final double[] brackets = bracketer.getBracketedPoints(deltaFunc, 0.5, 1.5, 0, 5);
return rootFinder.getRoot(deltaFunc, brackets[0], brackets[1]);
}
};
}
@SuppressWarnings("unused")
private Surface<Double, Double, Double> toDeltaProxySurface(final LocalVolatilitySurface<?> lv) {
final ForwardCurve fc = MARKET_DATA.getForwardCurve();
final Function<Double, Double> func = new Function<Double, Double>() {
@SuppressWarnings("synthetic-access")
@Override
public Double evaluate(final Double... tx) {
final double t = tx[0];
final double x = tx[1];
final double f = fc.getForward(t);
final double k = f * Math.exp(-x * Math.sqrt(t));
return lv.getVolatility(t, k);
}
};
return FunctionalDoublesSurface.from(func);
}
private Surface<Double, Double, Double> toDeltaProxySurface(final BlackVolatilitySurface<?> lv) {
final ForwardCurve fc = MARKET_DATA.getForwardCurve();
final Function<Double, Double> func = new Function<Double, Double>() {
@SuppressWarnings("synthetic-access")
@Override
public Double evaluate(final Double... tx) {
final double t = tx[0];
final double x = tx[1];
final double f = fc.getForward(t);
final double k = f * Math.exp(-x * Math.sqrt(t));
return lv.getVolatility(t, k);
}
};
return FunctionalDoublesSurface.from(func);
}
}