/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.equity.variance;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
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.EquityVarianceSwapForwardPurePDE;
import com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapMonteCarloCalculator;
import com.opengamma.analytics.financial.equity.variance.pricing.EquityVarianceSwapStaticReplication;
import com.opengamma.analytics.financial.equity.variance.pricing.VarianceSwapPureMonteCarloCalculator;
import com.opengamma.analytics.financial.equity.variance.pricing.VolatilitySurfaceConverter;
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.LocalVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.local.PureLocalVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.smile.function.MultiHorizonMixedLogNormalModelData;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.surface.MixedLogNormalVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.surface.PureImpliedVolatilitySurface;
import com.opengamma.analytics.math.FunctionUtils;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function;
import com.opengamma.analytics.math.surface.ConstantDoublesSurface;
import com.opengamma.analytics.math.surface.FunctionalDoublesSurface;
import com.opengamma.util.test.TestGroup;
/**
*
*/
@Test(groups = TestGroup.UNIT_SLOW)
public class VarianceSwapWithDividendsTest {
private static final boolean PRINT = false; //set to false for push
private static final boolean ASSERT = true; //set to true for push
private static final int N_SIMS = 10000; //put to 10,000 for push
final static int seed = 123;
private static final double MC_SD = 4.0;
private static final EquityVarianceSwapMonteCarloCalculator MC_CALCULATOR = new EquityVarianceSwapMonteCarloCalculator(seed, true, true);
private static final VarianceSwapPureMonteCarloCalculator MC_CALCULATOR_PURE = new VarianceSwapPureMonteCarloCalculator(seed, true, true);
private static final EquityVarianceSwapStaticReplication STATIC_REPLICATION = new EquityVarianceSwapStaticReplication();
private static final EquityVarianceSwapForwardPurePDE PDE_FWD_SOLVER = new EquityVarianceSwapForwardPurePDE();
private static final EquityVarianceSwapBackwardsPurePDE PDE_BKD_SOLVER = new EquityVarianceSwapBackwardsPurePDE();
private static final double EXPIRY = 1.5;
private static final double PURE_VOL = 0.5;
private static final double SPOT = 123;
private static final double DRIFT = 0.07;
private static final PureLocalVolatilitySurface PURE_LOCAL_VOL_FLAT = new PureLocalVolatilitySurface(ConstantDoublesSurface.from(PURE_VOL));
private static final PureImpliedVolatilitySurface PURE_IMPLIED_VOL_FLAT = new PureImpliedVolatilitySurface(ConstantDoublesSurface.from(PURE_VOL));
private static final MultiHorizonMixedLogNormalModelData MLN_DATA;
private static final YieldAndDiscountCurve DISCOUNT_CURVE = YieldCurve.from(ConstantDoublesCurve.from(DRIFT));
static {
final double[] weights = new double[] {0.8, 0.15, 0.05 };
final double[] sigmas = new double[] {0.3, 0.5, 0.9 };
final double[] mus = new double[] {0.04, 0.1, -0.2 };
MLN_DATA = new MultiHorizonMixedLogNormalModelData(weights, sigmas, mus);
}
@SuppressWarnings("unused")
@Test
public void checkTest() {
if (PRINT) {
System.out.println("VarianceSwapWithDividendsTest PRINT must be set to false");
}
if (!ASSERT) {
System.out.println("VarianceSwapWithDividendsTest ASSERT must be set to true, otherwise numbers are NOT tested");
}
if (N_SIMS > 10000) {
System.out.println("VarianceSwapWithDividendsTest Too many simulations for Ant tests - change N_SIMS to 10000");
}
}
@Test
public void noDividendsTest() {
final AffineDividends dividends = AffineDividends.noDividends();
testNumericsForFlatPureVol(dividends);
testNumerics(dividends, MLN_DATA, 1e-7);
}
@Test
public void proportionalOnlyTest() {
final double[] tau = new double[] {EXPIRY - 0.7, EXPIRY - 0.1, EXPIRY + 0.1 };
final double[] alpha = new double[3];
final double[] beta = new double[] {0.1, 0.1, 0.1 };
final AffineDividends dividends = new AffineDividends(tau, alpha, beta);
testNumericsForFlatPureVol(dividends);
testNumerics(dividends, MLN_DATA, 1e-7);
}
@Test
public void dividendsAfterExpiryTest() {
final double[] tau = new double[] {EXPIRY + 0.1, EXPIRY + 0.6 };
final double[] alpha = new double[] {0.1 * SPOT, 0.05 * SPOT };
final double[] beta = new double[] {0.05, 0.1 };
final AffineDividends dividends = new AffineDividends(tau, alpha, beta);
testNumericsForFlatPureVol(dividends);
testNumerics(dividends, MLN_DATA, 1e-7);
}
@Test
public void dividendsBeforeExpiryTest() {
final double[] tau = new double[] {0.85, 1.2 };
final double[] alpha = new double[] {0.3 * SPOT, 0.2 * SPOT };
final double[] beta = new double[] {0.1, 0.2 };
final AffineDividends dividends = new AffineDividends(tau, alpha, beta);
testNumericsForFlatPureVol(dividends);
testNumerics(dividends, MLN_DATA, 1e-7);
}
@Test
public void dividendsAtExpiryTest() {
final double[] tau = new double[] {1.2, EXPIRY };
final double[] alpha = new double[] {0.1 * SPOT, 0.05 * SPOT };
final double[] beta = new double[] {0.1, 0.2 };
final AffineDividends dividends = new AffineDividends(tau, alpha, beta);
// testNumericsForFlatPureVol(dividends);
testNumerics(dividends, MLN_DATA, 1e-7);
}
@Test
public void testMixedLogNormalVolSurface() {
final AffineDividends dividends = AffineDividends.noDividends();
final ForwardCurve fwdCurve = new ForwardCurve(SPOT, DRIFT);
final double sigma1 = 0.2;
final double sigma2 = 1.0;
final double w = 0.9;
final Function<Double, Double> surf = new Function<Double, Double>() {
@Override
public Double evaluate(final Double... x) {
final double t = x[0];
final double k = x[1];
@SuppressWarnings("synthetic-access")
final double fwd = fwdCurve.getForward(t);
final boolean isCall = k > fwd;
final double price = w * BlackFormulaRepository.price(fwd, k, t, sigma1, isCall) + (1 - w) * BlackFormulaRepository.price(fwd, k, t, sigma2, isCall);
return BlackFormulaRepository.impliedVolatility(price, fwd, k, t, isCall);
}
};
final BlackVolatilitySurfaceStrike surfaceStrike = new BlackVolatilitySurfaceStrike(FunctionalDoublesSurface.from(surf));
final double[] res = STATIC_REPLICATION.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, surfaceStrike);
final double rv = res[0] / EXPIRY;
final double expected = w * sigma1 * sigma1 + (1 - w) * sigma2 * sigma2;
assertEquals(expected, rv, 2e-6); //TODO this should be better
// PDE_BKD_SOLVER.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, );
}
/**
* Tests the Monte Carlo (which required a local volatility surface (strike)) against a static replication (which requires a pure implied volatility surface)
* @param dividends
*/
private void testNumericsForFlatPureVol(final AffineDividends dividends) {
final EquityDividendsCurvesBundle divCurves = new EquityDividendsCurvesBundle(SPOT, DISCOUNT_CURVE, dividends);
final LocalVolatilitySurfaceStrike localVol = VolatilitySurfaceConverter.convertLocalVolSurface(PURE_LOCAL_VOL_FLAT, divCurves);
//get the analytic values of the expected variance if there are no cash dividends
boolean canPriceAnalytic = true;
for (int i = 0; i < dividends.getNumberOfDividends(); i++) {
if (dividends.getAlpha(i) > 0.0) {
canPriceAnalytic = false;
break;
}
}
double anVar1 = 0;
double anVar2 = 0;
if (canPriceAnalytic) {
int index = 0;
double anCorr = 0;
final int n = dividends.getNumberOfDividends();
while (n > 0 && index < n && dividends.getTau(index) < EXPIRY) {
anCorr += FunctionUtils.square(Math.log(1 - dividends.getBeta(index)));
index++;
}
anVar1 = PURE_VOL * PURE_VOL;
anVar2 = anVar1 + anCorr / EXPIRY;
if (PRINT) {
System.out.format("Analytic: RV1 = %1$.8f RV2 = %2$.8f%n", anVar1, anVar2);
}
}
final double fT = divCurves.getF(EXPIRY);
//run Monte Carlo (simulating the actual stock process)
double[] res = MC_CALCULATOR.solve(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, localVol, N_SIMS);
final double mcST = res[0]; //The expected stock price at expiry
final double mcSDST = Math.sqrt(res[3]); //The Standard Deviation of the expected stock price at expiry
final double mcRV1 = res[1]; //The (annualised) expected variance correcting for dividends
final double mcVarRV1 = res[4];// The variance of the expected variance correcting for dividends
final double mcRV2 = res[2]; //The (annualised) expected variance NOT correcting for dividends
final double mcVarRV2 = res[5]; //The variance of expected variance NOT correcting for dividends
// double mceK1 = (Math.sqrt(mcRV1) - mcVarRV1 / 8 / Math.pow(mcRV1, 1.5)); //very small bias correction applied here
// double sdK1 = Math.sqrt(mcVarRV1 / 4 / mcRV1 * (1 - mcVarRV1 / 16 / mcRV1 / mcRV1));
// double mceK2 = (Math.sqrt(mcRV2) - mcVarRV2 / 8 / Math.pow(mcRV2, 1.5)); //very small bias correction applied here
// double sdK2 = Math.sqrt(mcVarRV2 / 4 / mcRV2 * (1 - mcVarRV2 / 16 / mcRV2 / mcRV2));
if (PRINT) {
System.out.format("Monte Carlo: F_T = %1$.3f, s = %2$.3f+-%3$.3f RV1 = %4$.8f+-%5$.8f RV2 = %6$.8f+-%7$.8f%n", fT, mcST, mcSDST, mcRV1, Math.sqrt(mcVarRV1), mcRV2, Math.sqrt(mcVarRV2));
}
assertEquals("E[S_T]", fT, mcST, MC_SD * mcSDST); //number of standard deviations of MC error
if (canPriceAnalytic && ASSERT) {
assertEquals("Analytic V MC RV1", anVar1, mcRV1, MC_SD * Math.sqrt(mcVarRV1));
assertEquals("Analytic V MC RV2", anVar2, mcRV2, MC_SD * Math.sqrt(mcVarRV2));
}
//run Monte Carlo (simulating the PURE stock process)
res = MC_CALCULATOR_PURE.solve(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, PURE_LOCAL_VOL_FLAT, N_SIMS);
final double mcpST = res[0]; //see above for definition of these variables
final double mcpSDST = Math.sqrt(res[3]);
final double mcpRV1 = res[1];
final double mcpVarRV1 = res[4];
final double mcpRV2 = res[2];
final double mcpVarRV2 = res[5];
if (PRINT) {
System.out.format("Pure Monte Carlo: F_T = %1$.3f, s = %2$.3f+-%3$.3f RV1 = %4$.8f+-%5$.8f RV2 = %6$.8f+-%7$.8f%n", fT, mcpST, mcpSDST, mcpRV1, Math.sqrt(mcpVarRV1), mcpRV2,
Math.sqrt(mcpVarRV2));
}
if (ASSERT) {
assertEquals("E[S_T]", fT, mcpST, MC_SD * mcpSDST); //number of standard deviations of MC error
if (canPriceAnalytic) {
assertEquals("Analytic V MC (pure) RV1", anVar1, mcpRV1, MC_SD * Math.sqrt(mcpVarRV1));
assertEquals("Analytic V MC (pure) RV2", anVar2, mcpRV2, MC_SD * Math.sqrt(mcpVarRV2));
}
//if the same seed is used, the two Monte Carlos should differ only by the discretisation error from using a Euler scheme (Euler is exact for a flat local volatility)
assertEquals("MC (Pure) V MC RV1", mcpRV1, mcRV1, 1e-4);
assertEquals("MC (pure) V MC RV2", mcpRV2, mcRV2, 1e-3); //TODO should have a better tolerance than this
}
//To match up with Monte Carlo, make all dividend times an integer number of days
final int n = dividends.getNumberOfDividends();
final double[] tau = dividends.getTau();
final double dt = 1. / 252;
for (int i = 0; i < n; i++) {
final int steps = (int) (Math.ceil(tau[i] * 252));
tau[i] = steps * dt;
}
//calculate by static replication using prices of PURE put and call prices
res = STATIC_REPLICATION.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, PURE_IMPLIED_VOL_FLAT);
final double srpRV1 = res[0] / EXPIRY;
final double srpRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Static replication (pure): RV1 = %1$.8f RV2 = %2$.8f%n", srpRV1, srpRV2);
}
if (ASSERT) {
if (canPriceAnalytic) {
assertEquals("Analytic V Static Rep (pure) RV1", anVar1, srpRV1, 1e-8);
assertEquals("Analytic V Static Rep (pure) RV2", anVar2, srpRV2, 1e-8);
} else {
assertEquals("Static Rep (pure) V MC RV1", srpRV1, mcpRV1, MC_SD * Math.sqrt(mcpVarRV1));
assertEquals("Static Rep (pure) V MC RV2", srpRV2, mcpRV2, MC_SD * Math.sqrt(mcpVarRV2));
}
}
//calculate by static replication using actual prices of put and call prices (converted from pure prices)
//NOTE, this surface is converted from the (smooth, flat) pure implied vol surface, and will have the correct discontinuities at cash dividend dates
final BlackVolatilitySurfaceStrike impVol = VolatilitySurfaceConverter.convertImpliedVolSurface(PURE_IMPLIED_VOL_FLAT, divCurves);
res = STATIC_REPLICATION.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, impVol);
final double srRV1 = res[0] / EXPIRY;
final double srRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Static replication (real price): RV1 = %1$.8f RV2 = %2$.8f%n", srRV1, srRV2);
}
if (ASSERT) {
if (canPriceAnalytic) {
assertEquals("Analytic V Static Rep (real price) RV1", anVar1, srRV1, 1e-8);
assertEquals("Analytic V Static Rep (real price) RV2", anVar2, srRV2, 1e-8);
} else {
assertEquals("Static Rep (real price) V Static Rep (pure) RV1", srRV1, srpRV1, 2e-4); //TODO These should be closer
assertEquals("Static Rep (real prcie) V Static Rep (pure) RV2", srRV2, srpRV2, 5e-5);
}
}
//calculate by solving the forward PDE
res = PDE_FWD_SOLVER.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, PURE_LOCAL_VOL_FLAT);
final double fpdeRV1 = res[0] / EXPIRY;
final double fpdeRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Forward PDE: RV1 = %1$.8f RV2 = %2$.8f%n", fpdeRV1, fpdeRV2);
}
if (ASSERT) {
if (canPriceAnalytic) {
assertEquals("Analytic V Forward PDE RV1", anVar1, fpdeRV1, 2e-5);
assertEquals("Analytic V Forward PDE RV2", anVar2, fpdeRV2, 2e-5);
} else {
assertEquals("Static Rep (pure) V Forward PDE RV1", srpRV1, fpdeRV1, 2e-5);
assertEquals("Static Rep (pure) V Forward PDE RV2", srpRV2, fpdeRV2, 5e-5);
}
}
//calculate by solving the backwards PDE
res = PDE_BKD_SOLVER.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, PURE_LOCAL_VOL_FLAT);
final double bpdeRV1 = res[0] / EXPIRY;
final double bpdeRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Backwards PDE: RV1 = %1$.8f RV2 = %2$.8f%n", bpdeRV1, bpdeRV2);
}
if (ASSERT) {
if (canPriceAnalytic) {
assertEquals("Analytic V Forward PDE RV1", anVar1, bpdeRV1, 1e-7);
assertEquals("Analytic V Forward PDE RV2", anVar2, bpdeRV2, 1e-7);
} else {
assertEquals("Static Rep (pure) V Backwards PDE RV1", srpRV1, bpdeRV1, 1e-4);
assertEquals("Static Rep (pure) V Backwards PDE RV2", srpRV2, bpdeRV2, 1e-4);
}
}
}
/**
* Test the various numerical scheme when we have extraneously given pure (implied and local) volatility surfaces
* @param dividends The dividend structure
* @param pImpVol The pure implied volatility surface
* @param plv The pure local volatility surface
*/
private void testNumerics(final AffineDividends dividends, final MultiHorizonMixedLogNormalModelData data, final double defaultTol) {
final ForwardCurve fc = new ForwardCurve(1.0);
final LocalVolatilitySurfaceStrike lv = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(fc, data);
final PureLocalVolatilitySurface plv = new PureLocalVolatilitySurface(lv.getSurface());
final BlackVolatilitySurfaceStrike iv = MixedLogNormalVolatilitySurface.getImpliedVolatilitySurface(fc, data);
final PureImpliedVolatilitySurface piv = new PureImpliedVolatilitySurface(iv.getSurface());
final double[] weights = data.getWeights();
final double[] mus = data.getMus();
final double[] sigmas = data.getVolatilities();
final double m = weights.length;
final int n = dividends.getNumberOfDividends();
//get the analytic values of the expected variance if there are no cash dividends
boolean canPriceAnalytic = true;
for (int i = 0; i < n; i++) {
if (dividends.getAlpha(i) > 0.0) {
canPriceAnalytic = false;
break;
}
}
double expDivCorr = 0.0;
double expDivNoCorr = 0.0;
if (canPriceAnalytic) {
double sum1 = 0.0;
double sum2 = 0.0;
for (int i = 0; i < m; i++) {
sum1 += weights[i] * Math.exp(mus[i] * EXPIRY);
sum2 += weights[i] * (mus[i] - sigmas[i] * sigmas[i] / 2);
}
expDivCorr = 2 * (Math.log(sum1) / EXPIRY - sum2);
double anCorr = 0;
int index = 0;
while (n > 0 && index < n && dividends.getTau(index) < EXPIRY) {
anCorr += FunctionUtils.square(Math.log(1 - dividends.getBeta(index)));
index++;
}
expDivNoCorr = expDivCorr + anCorr / EXPIRY;
}
testNumerics(canPriceAnalytic, expDivCorr, expDivNoCorr, dividends, piv, plv, defaultTol);
}
/**
* Test the various numerical scheme when we have extraneously given pure (implied and local) volatility surfaces
* @param dividends The dividend structure
* @param pImpVol The pure implied volatility surface
* @param plv The pure local volatility surface
*/
@SuppressWarnings("unused")
private void testNumerics(final boolean isAnalytic, final double expDivCorr, final double expDivNoCorr, final AffineDividends dividends, final PureImpliedVolatilitySurface pImpVol,
final PureLocalVolatilitySurface plv, final double defaultTol) {
if (PRINT && isAnalytic) {
System.out.format("Analytic: RV1 = %1$.8f RV2 = %2$.8f%n", expDivCorr, expDivNoCorr);
}
//convert the pure (implied and local)
final EquityDividendsCurvesBundle divCurves = new EquityDividendsCurvesBundle(SPOT, DISCOUNT_CURVE, dividends);
final LocalVolatilitySurfaceStrike localVol = VolatilitySurfaceConverter.convertLocalVolSurface(plv, divCurves);
final BlackVolatilitySurfaceStrike impVol = VolatilitySurfaceConverter.convertImpliedVolSurface(pImpVol, divCurves);
final double fT = divCurves.getF(EXPIRY);
//run Monte Carlo (simulating the PURE stock process)
double[] res = MC_CALCULATOR_PURE.solve(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, plv, N_SIMS);
final double mcpST = res[0]; //The expected stock price at expiry
final double mcpSDST = Math.sqrt(res[3]); //The Standard Deviation of the expected stock price at expiry
final double mcpRV1 = res[1]; //The (annualised) expected variance correcting for dividends
final double mcpVarRV1 = res[4];// The variance of the expected variance correcting for dividends
final double mcpRV2 = res[2]; //The (annualised) expected variance NOT correcting for dividends
final double mcpVarRV2 = res[5]; //The variance of expected variance NOT correcting for dividends
if (PRINT) {
System.out.format("Pure Monte Carlo: F_T = %1$.3f, s = %2$.3f+-%3$.3f RV1 = %4$.8f+-%5$.8f RV2 = %6$.8f+-%7$.8f%n", fT, mcpST, mcpSDST, mcpRV1, Math.sqrt(mcpVarRV1), mcpRV2,
Math.sqrt(mcpVarRV2));
}
if (ASSERT) {
assertEquals("E[S_T]", fT, mcpST, MC_SD * mcpSDST); //number of standard deviations of MC error
if (isAnalytic) {
assertEquals("Analytic V MC RV1", expDivCorr, mcpRV1, MC_SD * Math.sqrt(mcpVarRV1));
assertEquals("Analytic V MC RV2", expDivNoCorr, mcpRV2, MC_SD * Math.sqrt(mcpVarRV2));
}
}
//run the Monte Carlo (simulating the ACTUAL stock process)
res = MC_CALCULATOR.solve(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, localVol, N_SIMS);
final double mcST = res[0]; //see above for definition of these variables
final double mcSDST = Math.sqrt(res[3]);
final double mcRV1 = res[1];
final double mcVarRV1 = res[4];
final double mcRV2 = res[2];
final double mcVarRV2 = res[5];
if (PRINT) {
System.out.format("Monte Carlo: F_T = %1$.3f, s = %2$.3f+-%3$.3f RV1 = %4$.8f+-%5$.8f RV2 = %6$.8f+-%7$.8f%n", fT, mcST, mcSDST, mcRV1, Math.sqrt(mcVarRV1), mcRV2,
Math.sqrt(mcVarRV2));
}
if (ASSERT) {
assertEquals("E[S_T]", fT, mcST, MC_SD * mcSDST); //number of standard deviations of MC error
if (isAnalytic) {
assertEquals("Analytic V MC RV1", expDivCorr, mcRV1, MC_SD * Math.sqrt(mcVarRV1));
assertEquals("Analytic V MC RV2", expDivNoCorr, mcRV2, MC_SD * Math.sqrt(mcVarRV2));
}
}
//calculate by static replication using prices of PURE put and call prices (from the pure implied volatility surface)
res = STATIC_REPLICATION.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, pImpVol);
final double srpRV1 = res[0] / EXPIRY;
final double srpRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Static replication (pure): RV1 = %1$.8f RV2 = %2$.8f%n", srpRV1, srpRV2);
}
if (ASSERT) {
if (isAnalytic) {
assertEquals("Analytic V Static Rep (pure) RV1", expDivCorr, srpRV1, defaultTol);
assertEquals("Analytic V Static Rep (pure) RV2", expDivNoCorr, srpRV2, defaultTol);
} else {
assertEquals("MC V Static Rep (pure) RV1", mcpRV1, srpRV1, MC_SD * Math.sqrt(mcpVarRV1));
assertEquals("MC V Static Rep (pure) RV2", mcpRV2, srpRV2, MC_SD * Math.sqrt(mcpVarRV2));
}
}
//calculate by static replication using actual prices of put and call prices (from the (converted) implied volatility surface)
res = STATIC_REPLICATION.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, impVol);
final double srRV1 = res[0] / EXPIRY;
final double srRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Static replication (real price): RV1 = %1$.8f RV2 = %2$.8f%n", srRV1, srRV2);
}
if (ASSERT) {
if (isAnalytic) {
assertEquals("Analytic V Static Rep (actual) RV1", expDivCorr, srRV1, defaultTol);
assertEquals("Analytic V Static Rep (actual) RV2", expDivNoCorr, srRV2, defaultTol);
} else {
assertEquals("MC V Static Rep (actual) RV1", mcRV1, srRV1, MC_SD * Math.sqrt(mcVarRV1));
assertEquals("MC V Static Rep (actual) RV2", mcRV2, srRV2, MC_SD * Math.sqrt(mcVarRV2));
}
}
//calculate by solving the forward PDE using the pure local volatility to get pure option prices, before pricing the variance swaps via static replication
res = PDE_FWD_SOLVER.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, plv);
final double fpdeRV1 = res[0] / EXPIRY;
final double fpdeRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Forward PDE: RV1 = %1$.8f RV2 = %2$.8f%n", fpdeRV1, fpdeRV2);
}
if (ASSERT) {
if (isAnalytic) {
assertEquals("Analytic V Forward PDE RV1", expDivCorr, fpdeRV1, 1e4 * defaultTol); //poor tolerance
assertEquals("Analytic V Forward PDE RV2", expDivNoCorr, fpdeRV2, 1e4 * defaultTol);
} else {
assertEquals("Static Rep (pure) V Forward PDE RV1", srpRV1, fpdeRV1, 1e4 * defaultTol);
assertEquals("Static Rep (pure) V Forward PDE RV2", srpRV2, fpdeRV2, 1e4 * defaultTol);
}
}
//calculate by solving the backwards PDE using the pure local volatility surface
res = PDE_BKD_SOLVER.expectedVariance(SPOT, DISCOUNT_CURVE, dividends, EXPIRY, plv);
final double bpdeRV1 = res[0] / EXPIRY;
final double bpdeRV2 = res[1] / EXPIRY;
if (PRINT) {
System.out.format("Backwards PDE: RV1 = %1$.8f RV2 = %2$.8f%n", bpdeRV1, bpdeRV2);
}
if (ASSERT) {
if (isAnalytic) {
assertEquals("Analytic V backwards PDE RV1", expDivCorr, bpdeRV1, 1e4 * defaultTol);
assertEquals("Analytic V backwards PDE RV2", expDivNoCorr, bpdeRV2, 1e4 * defaultTol);
} else {
assertEquals("Static Rep (pure) V Backwards (pure) PDE RV1", srpRV1, bpdeRV1, 1e5 * defaultTol);
assertEquals("Static Rep (pure) V Backwards (pure) PDE RV2", srpRV2, bpdeRV2, 1e5 * defaultTol);
}
}
}
}