/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.volatility.surface;
import static org.testng.AssertJUnit.assertEquals;
import java.util.Map;
import org.testng.annotations.Test;
import cern.jet.random.engine.MersenneTwister;
import cern.jet.random.engine.MersenneTwister64;
import cern.jet.random.engine.RandomEngine;
import com.opengamma.analytics.financial.equity.variance.pricing.AffineDividends;
import com.opengamma.analytics.financial.equity.variance.pricing.EquityDividendsCurvesBundle;
import com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapBackwardsPurePDE;
import com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapStaticReplication;
import com.opengamma.analytics.financial.equity.variance.pricing.VolatilitySurfaceConverter;
import com.opengamma.analytics.financial.model.finitedifference.BoundaryCondition;
import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DCoefficients;
import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDE1DStandardCoefficients;
import com.opengamma.analytics.financial.model.finitedifference.ConvectionDiffusionPDESolver;
import com.opengamma.analytics.financial.model.finitedifference.DoubleExponentialMeshing;
import com.opengamma.analytics.financial.model.finitedifference.ExponentialMeshing;
import com.opengamma.analytics.financial.model.finitedifference.HyperbolicMeshing;
import com.opengamma.analytics.financial.model.finitedifference.MeshingFunction;
import com.opengamma.analytics.financial.model.finitedifference.NeumannBoundaryCondition;
import com.opengamma.analytics.financial.model.finitedifference.PDE1DDataBundle;
import com.opengamma.analytics.financial.model.finitedifference.PDEGrid1D;
import com.opengamma.analytics.financial.model.finitedifference.PDEResults1D;
import com.opengamma.analytics.financial.model.finitedifference.PDETerminalResults1D;
import com.opengamma.analytics.financial.model.finitedifference.ThetaMethodFiniteDifference;
import com.opengamma.analytics.financial.model.finitedifference.applications.InitialConditionsProvider;
import com.opengamma.analytics.financial.model.finitedifference.applications.PDE1DCoefficientsProvider;
import com.opengamma.analytics.financial.model.finitedifference.applications.PDEUtilityTools;
import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.local.DupireLocalVolatilityCalculator;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceConverter;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceMoneyness;
import com.opengamma.analytics.financial.model.volatility.local.LocalVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.local.PureLocalVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SurfaceArrayUtils;
import com.opengamma.analytics.financial.model.volatility.smile.function.MultiHorizonMixedLogNormalModelData;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.interpolation.DoubleQuadraticInterpolator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle;
/**
*
*/
public class MixedLogNormalVolatilitySurfaceTest {
private static final RandomEngine RANDOM = new MersenneTwister64(MersenneTwister.DEFAULT_SEED);
@Test(enabled = false)
public void printTest() {
System.out.println("MixedLogNormalVolatilitySurfaceTest. printTest");
final double t = 1.5;
final double spot = 100.0;
final double r = 0.05;
final ForwardCurve fc = new ForwardCurve(spot, r);
final YieldAndDiscountCurve discountCurve = new YieldCurve("test", ConstantDoublesCurve.from(r));
double[] w = new double[] {0.7, 0.25, 0.05 };
double[] sigma = new double[] {0.3, 0.6, 1.0 };
double[] mu = new double[] {0.0, 0.3, -0.5 };
MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(w, sigma, mu);
BlackVolatilitySurfaceStrike ivs = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fc, data);
PDEUtilityTools.printSurface("imp vol", ivs.getSurface(), 0.01, 2.0, spot * 0.1, spot * 3.0);
LocalVolatilitySurfaceStrike lvs = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fc, data);
PDEUtilityTools.printSurface("local vol", lvs.getSurface(), 0.00, 2.0, spot * 0.1, spot * 3.0);
DupireLocalVolatilityCalculator cal = new DupireLocalVolatilityCalculator();
LocalVolatilitySurfaceStrike lv2 = cal.getLocalVolatility(ivs, fc);
// PDEUtilityTools.printSurface("local vo2l", lv2.getSurface(), 0.01, 2.0, spot * 0.3, spot * 3.0);
System.out.println("lv: " + lvs.getVolatility(2.0, 1.7 * spot));
System.out.println("lv2: " + lv2.getVolatility(2.0, 1.7 * spot));
EquityVarianceSwapStaticReplication vsPricer = new EquityVarianceSwapStaticReplication();
double[] res = vsPricer.expectedVariance(spot, discountCurve, AffineDividends.noDividends(), t, ivs);
System.out.println(Math.sqrt(res[0] / t));
double sum1 = 0.0;
double sum2 = 0.0;
for (int i = 0; i < 3; i++) {
sum1 += w[i] * Math.exp(mu[i] * t);
sum2 += w[i] * t * (mu[i] - sigma[i] * sigma[i] / 2);
}
double temp = Math.sqrt(2 * (Math.log(sum1) - sum2) / t);
System.out.println("expected: " + temp);
PureLocalVolatilitySurface plv = VolatilitySurfaceConverter.convertLocalVolSurface(lv2, new EquityDividendsCurvesBundle(spot, discountCurve, AffineDividends.noDividends()));
EquityVarianceSwapBackwardsPurePDE backCal = new EquityVarianceSwapBackwardsPurePDE();
res = backCal.expectedVariance(spot, discountCurve, AffineDividends.noDividends(), t, plv);
System.out.println(Math.sqrt(res[0] / t));
double vol = lvs.getVolatility(0.0, 25.0);
System.out.println(vol);
}
@Test
public void flatTest() {
final double spot = 123.0;
final double r = 0.05;
final ForwardCurve fc = new ForwardCurve(spot, r);
final double vol = 0.3;
double[] w = new double[] {0.6, 0.4 };
double[] sigma = new double[] {vol, vol };
MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(w, sigma);
BlackVolatilitySurfaceStrike ivs = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fc, data);
LocalVolatilitySurfaceStrike lvs = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fc, data);
for (int i = 0; i < 100; i++) {
double t = 5.0 * RANDOM.nextDouble();
//strikes in range +- 8-sigma
double a = 16 * (RANDOM.nextDouble() - 0.5);
double k = spot * Math.exp((r - vol * vol / 2) * t + Math.sqrt(t) * vol * a);
double iv = ivs.getVolatility(t, k);
double lv = lvs.getVolatility(t, k);
assertEquals("implied volatility t=" + t + ", k=" + k, vol, iv, 1e-14);
assertEquals("local volatilityt=" + t + ", k=" + k, vol, lv, 1e-14);
}
}
@Test
public void nonflatTest1() {
DupireLocalVolatilityCalculator dupire = new DupireLocalVolatilityCalculator();
final double spot = 0.03;
final double r = 0.05;
final ForwardCurve fc = new ForwardCurve(spot, r);
final double vol = 0.3;
double[] w = new double[] {0.8, 0.2 };
double[] sigma = new double[] {0.2, 0.7 };
MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(w, sigma);
BlackVolatilitySurfaceStrike ivs = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fc, data);
LocalVolatilitySurfaceStrike lvs = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fc, data);
LocalVolatilitySurfaceStrike lvs2 = dupire.getLocalVolatility(ivs, fc);
for (int i = 0; i < 100; i++) {
double t = 5.0 * RANDOM.nextDouble();
//strikes in range +- 8-sigma
double a = 16 * (RANDOM.nextDouble() - 0.5);
double k = spot * Math.exp((r - vol * vol / 2) * t + Math.sqrt(t) * vol * a);
double lv = lvs.getVolatility(t, k);
double lv2 = lvs2.getVolatility(t, k);
assertEquals("Local volatility t=" + t + ", k=" + k, lv, lv2, 1e-3); //loss a lot of accuracy going via Dupire formula (since this used finite difference on the implied vol surface)
}
}
@Test
public void nonflatTest2() {
DupireLocalVolatilityCalculator dupire = new DupireLocalVolatilityCalculator();
final double spot = 0.03;
final double r = 0.10;
final ForwardCurve fc = new ForwardCurve(spot, r);
final double vol = 0.3;
double[] w = new double[] {0.9, 0.1 };
double[] sigma = new double[] {0.2, 0.7 };
double[] mu = new double[] {0.1, -0.1 };
MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(w, sigma, mu);
BlackVolatilitySurfaceStrike ivs = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fc, data);
LocalVolatilitySurfaceStrike lvs = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fc, data);
LocalVolatilitySurfaceStrike lvs2 = dupire.getLocalVolatility(ivs, fc);
for (int i = 0; i < 100; i++) {
double t = 5.0 * RANDOM.nextDouble();
//strikes in range +- 8-sigma
double a = 16 * (RANDOM.nextDouble() - 0.5);
double k = spot * Math.exp((r - vol * vol / 2) * t + Math.sqrt(t) * vol * a);
double lv = lvs.getVolatility(t, k);
double lv2 = lvs2.getVolatility(t, k);
assertEquals("Local volatility t=" + t + ", k=" + k, lv, lv2, 1e-3); //loss a lot of accuracy going via Dupire formula (since this used finite difference on the implied vol surface)
}
//lvs.getVolatility(3.86, 0.177);
// PDEUtilityTools.printSurface("implied vol", ivs.getSurface(), 0.01, 4.0, 0.1 * spot, 10.0 * spot);
// PDEUtilityTools.printSurface("local vol", lvs.getSurface(), 0.01, 5.0, 0.1 * spot, 10.0 * spot);
// PDEUtilityTools.printSurface("local vo2l", lvs2.getSurface(), 0.01, 4.0, 0.1 * spot, 10.0 * spot);
}
/**
* Test the pricing of a 1D option with the local volatility surface produced by the mixed log-normal model solving both the forward and backwards PDE, and compare
* with the result from the implied volatility surface. Neither solving the forward nor the backwards PDE give great accuracy
*/
@Test
public void shortDatedOptionTest() {
final ConvectionDiffusionPDESolver solver = new ThetaMethodFiniteDifference(0.5, false);
final PDE1DCoefficientsProvider pdeProvider = new PDE1DCoefficientsProvider();
final InitialConditionsProvider initialConProvider = new InitialConditionsProvider();
//set up the mixed log-normal model
final double[] weights = new double[] {0.1, 0.8, 0.1 };
final double[] sigmas = new double[] {0.15, 0.5, 0.9 };
final double[] mus = new double[] {-0.1, 0, 0.1 };
final MultiHorizonMixedLogNormalModelData mlnData = new MultiHorizonMixedLogNormalModelData(weights, sigmas, mus);
final double spot = 100.;
final double r = 0.1;
final ForwardCurve fwdCurve = new ForwardCurve(spot, r);
final double t = 1.0 / 365.;
final double rootT = Math.sqrt(t);
final double fwd = fwdCurve.getForward(t);
BlackVolatilitySurfaceStrike ivs = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fwdCurve, mlnData);
LocalVolatilitySurfaceStrike lvs = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fwdCurve, mlnData);
LocalVolatilitySurfaceMoneyness lvsm = LocalVolatilitySurfaceConverter.toMoneynessSurface(lvs, fwdCurve);
// PDEUtilityTools.printSurface("imp vol", ivs.getSurface(), 0, t, 0.9, 1.1);
// DupireLocalVolatilityCalculator dupire = new DupireLocalVolatilityCalculator();
// LocalVolatilitySurfaceMoneyness dlv = dupire.getLocalVolatility(BlackVolatilitySurfaceConverter.toMoneynessSurface(ivs, fwdCurve));
// PDEUtilityTools.printSurface("local vol (dupire)", dlv.getSurface(), 0, t, 0.99, 1.01);
// PDEUtilityTools.printSurface("local vol", lvsm.getSurface(), 0, t, 0.99, 1.01);
//set up for solving the forward PDE
//TODO shunt this setup into its own class
ConvectionDiffusionPDE1DStandardCoefficients pde = pdeProvider.getForwardLocalVol(lvsm);
Function1D<Double, Double> initialCond = initialConProvider.getForwardCallPut(true);
double xL = 0.8;
double xH = 1.2;
BoundaryCondition lower = new NeumannBoundaryCondition(-1.0, xL, true);
BoundaryCondition upper = new NeumannBoundaryCondition(0.0, xH, false);
final MeshingFunction spaceMeshF = new HyperbolicMeshing(xL, xH, 1.0, 200, 0.001);
final MeshingFunction timeMeshF = new ExponentialMeshing(0, t, 50, 4.0);
final MeshingFunction timeMeshB = new DoubleExponentialMeshing(0, t, t / 2, 50, 2.0, -4.0);
final PDEGrid1D grid = new PDEGrid1D(timeMeshF, spaceMeshF);
PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> dbF = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, initialCond, lower, upper, grid);
PDETerminalResults1D res = (PDETerminalResults1D) solver.solve(dbF);
final double minK = Math.exp(-6 * rootT);
final double maxK = Math.exp(6 * rootT);
Map<Double, Double> vols = PDEUtilityTools.priceToImpliedVol(fwdCurve, t, res, minK, maxK, true);
DoubleQuadraticInterpolator1D interpolator = Interpolator1DFactory.DOUBLE_QUADRATIC_INSTANCE;
Interpolator1DDataBundle idb = interpolator.getDataBundle(vols);
//set up for solving backwards PDE
ConvectionDiffusionPDE1DStandardCoefficients pdeB = pdeProvider.getBackwardsLocalVol(t, lvsm);
double sL = xL * spot;
double sH = xH * spot;
final MeshingFunction spaceMeshB = new HyperbolicMeshing(sL, sH, spot, 200, 0.001);
final PDEGrid1D gridB = new PDEGrid1D(timeMeshB, spaceMeshB);
int index = SurfaceArrayUtils.getLowerBoundIndex(gridB.getSpaceNodes(), spot);
double s1 = gridB.getSpaceNode(index);
double s2 = gridB.getSpaceNode(index + 1);
final double w = (s2 - spot) / (s2 - s1);
//solve a separate backwards PDE for each strike
for (int i = 0; i < 10; i++) {
double z = -5 + i;
double k = spot * Math.exp(0.4 * rootT * z);
double x = k / fwd;
double vol = ivs.getVolatility(t, k);
double volFPDE = interpolator.interpolate(idb, x);
boolean isCall = (k >= fwd);
BoundaryCondition lowerB = new NeumannBoundaryCondition(isCall ? 0 : -1, sL, true);
BoundaryCondition upperB = new NeumannBoundaryCondition(isCall ? 1 : 0, sH, false);
Function1D<Double, Double> bkdIC = initialConProvider.getEuropeanPayoff(k, isCall);
PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> dbB = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pdeB, bkdIC, lowerB, upperB, gridB);
PDEResults1D resB = solver.solve(dbB);
double price1 = resB.getFunctionValue(index);
double price2 = resB.getFunctionValue(index + 1);
double vol1 = BlackFormulaRepository.impliedVolatility(price1, s1, k, t, isCall);
double vol2 = BlackFormulaRepository.impliedVolatility(price2, s2, k, t, isCall);
double volBPDE = w * vol1 + (1 - w) * vol2;
// double price = BlackFormulaRepository.price(fwd, k, t, vol, isCall);
// System.out.println(x + "\t" + vol + "\t" + volFPDE + "\t" + volBPDE);
assertEquals("forward PDE " + x, vol, volFPDE, 4e-3); //40bps error
assertEquals("backwards PDE " + x, vol, volBPDE, 5e-3); //50bps error TODO - why is this so bad
}
}
}