/**
* Copyright (C) 2009 - 2011 by OpenGamma Inc.
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.finitedifference;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.finitedifference.applications.CoupledPDEDataBundleProvider;
import com.opengamma.analytics.financial.model.finitedifference.applications.TwoStateMarkovChainDataBundle;
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.option.pricing.analytic.formula.BlackFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption;
import com.opengamma.analytics.financial.model.volatility.BlackImpliedVolatilityFormula;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.function.Function1D;
/**
* Tests on a pair of backwards Black-Scholes PDEs. The model is a Black-Scholes SDE where the volatility can take one of two values
* depending on the state of a hidden Markov chain. Degenerate (both vols the same) and uncoupled cases are tested along with a comparison
* to Monte Carlo.
*/
@SuppressWarnings("unused")
public class CoupledFiniteDifferenceTest {
private static final CoupledPDEDataBundleProvider PDE_DATA_PROVIDER = new CoupledPDEDataBundleProvider();
private static final BlackImpliedVolatilityFormula BLACK_IMPLIED_VOL = new BlackImpliedVolatilityFormula();
private static BoundaryCondition LOWER;
private static BoundaryCondition UPPER;
private static Function1D<Double, Double> INITIAL_COND;
private static final double SPOT = 100;
private static final ForwardCurve FORWARD;
private static final double STRIKE;
private static final double T = 5.0;
private static final double RATE = 0.0; //TODO this is not working properly with non-zero rate. Why?
private static final YieldAndDiscountCurve YIELD_CURVE = YieldCurve.from(ConstantDoublesCurve.from(RATE));
private static final double VOL1 = 0.15;//0.2;
private static final double VOL2 = 0.70;
private static final EuropeanVanillaOption OPTION;
static {
FORWARD = new ForwardCurve(SPOT, RATE);
STRIKE = FORWARD.getForward(T); // ATM option
OPTION = new EuropeanVanillaOption(FORWARD.getForward(T), T, true); // true option
LOWER = new DirichletBoundaryCondition(0.0, 0.0);// call is worth 0 when stock falls to zero
//UPPER = new DirichletBoundaryCondition(10 * SPOT - STRIKE, 10.0 * SPOT);
UPPER = new NeumannBoundaryCondition(1.0, 10 * STRIKE, false);
// UPPER = new FixedSecondDerivativeBoundaryCondition(0.0, 10 * FORWARD, false);
INITIAL_COND = new Function1D<Double, Double>() {
@Override
public Double evaluate(Double x) {
return Math.max(0, x - STRIKE);
}
};
}
@Test
public void testNoCoupling() {
//making theta 0.55 (rather than 0.5 Crank-Nicolson) cuts down ATM oscillations
final CoupledFiniteDifference solver = new CoupledFiniteDifference(0.5, false);
final double lambda12 = 0.0;
final double lambda21 = 0.0;
final double p0 = 0.5;
TwoStateMarkovChainDataBundle chainData = new TwoStateMarkovChainDataBundle(VOL1, VOL2, lambda12, lambda21, p0);
ConvectionDiffusionPDE1DCoupledCoefficients[] pdeData = PDE_DATA_PROVIDER.getCoupledBackwardsPair(FORWARD, T, chainData);
final int timeNodes = 20;
final int spaceNodes = 150;
final double lowerMoneyness = 0.4;
final double upperMoneyness = 2.5;
final MeshingFunction timeMesh = new ExponentialMeshing(0, T, timeNodes, 5.0);
final MeshingFunction spaceMesh = new HyperbolicMeshing(LOWER.getLevel(), UPPER.getLevel(), OPTION.getStrike(), spaceNodes, 0.1);
// MeshingFunction spaceMesh = new ExponentialMeshing(LOWER.getLevel(), UPPER.getLevel(), spaceNodes, 0.0);
final double[] timeGrid = new double[timeNodes];
for (int n = 0; n < timeNodes; n++) {
timeGrid[n] = timeMesh.evaluate(n);
}
final double[] spaceGrid = new double[spaceNodes];
for (int i = 0; i < spaceNodes; i++) {
spaceGrid[i] = spaceMesh.evaluate(i);
}
final PDEGrid1D grid = new PDEGrid1D(timeGrid, spaceGrid);
CoupledPDEDataBundle d1 = new CoupledPDEDataBundle(pdeData[0], INITIAL_COND, LOWER, UPPER, grid);
CoupledPDEDataBundle d2 = new CoupledPDEDataBundle(pdeData[1], INITIAL_COND, LOWER, UPPER, grid);
final PDEResults1D[] res = solver.solve(d1, d2);
final double df = YIELD_CURVE.getDiscountFactor(T);
final int n = res[0].getNumberSpaceNodes();
for (int i = 0; i < n; i++) {
final double spot = res[0].getSpaceValue(i);
final double price1 = res[0].getFunctionValue(i);
final double price2 = res[1].getFunctionValue(i);
final double moneyness = spot / OPTION.getStrike();
final BlackFunctionData data = new BlackFunctionData(spot / df, df, 0.0);
double impVol1;
try {
impVol1 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price1);
} catch (final Exception e) {
impVol1 = 0.0;
}
double impVol2;
try {
impVol2 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price2);
} catch (final Exception e) {
impVol2 = 0.0;
}
// System.out.println(spot + "\t" + price1 + "\t" + price2 + "\t" + impVol1 + "\t" + impVol2);
if (moneyness >= lowerMoneyness && moneyness <= upperMoneyness) {
assertEquals(VOL1, impVol1, 1e-3);
assertEquals(VOL2, impVol2, 5e-3);
}
}
}
@Test
public void testDegenerate() {
final CoupledFiniteDifference solver = new CoupledFiniteDifference();
final double lambda12 = 0.2;
final double lambda21 = 0.5;
TwoStateMarkovChainDataBundle chainData = new TwoStateMarkovChainDataBundle(VOL1, VOL1, lambda12, lambda21, 0.5);
ConvectionDiffusionPDE1DCoupledCoefficients[] pdeData = PDE_DATA_PROVIDER.getCoupledBackwardsPair(FORWARD, T, chainData);
final int timeNodes = 20;
final int spaceNodes = 150;
final double lowerMoneyness = 0.4;
final double upperMoneyness = 2.5;
final MeshingFunction timeMesh = new ExponentialMeshing(0, T, timeNodes, 5.0);
final MeshingFunction spaceMesh = new HyperbolicMeshing(LOWER.getLevel(), UPPER.getLevel(), OPTION.getStrike(), spaceNodes, 0.1);
// MeshingFunction spaceMesh = new ExponentialMeshing(LOWER.getLevel(), UPPER.getLevel(), spaceNodes, 0.0);
//
final double[] timeGrid = new double[timeNodes];
for (int n = 0; n < timeNodes; n++) {
timeGrid[n] = timeMesh.evaluate(n);
}
final double[] spaceGrid = new double[spaceNodes];
for (int i = 0; i < spaceNodes; i++) {
spaceGrid[i] = spaceMesh.evaluate(i);
}
final PDEGrid1D grid = new PDEGrid1D(timeGrid, spaceGrid);
final CoupledPDEDataBundle d1 = new CoupledPDEDataBundle(pdeData[0], INITIAL_COND, LOWER, UPPER, grid);
final CoupledPDEDataBundle d2 = new CoupledPDEDataBundle(pdeData[1], INITIAL_COND, LOWER, UPPER, grid);
final PDEResults1D[] res = solver.solve(d1, d2);
final double df = YIELD_CURVE.getDiscountFactor(T);
final int n = res[0].getNumberSpaceNodes();
for (int i = 0; i < n; i++) {
final double spot = res[0].getSpaceValue(i);
final double price1 = res[0].getFunctionValue(i);
final double price2 = res[1].getFunctionValue(i);
final double moneyness = spot / OPTION.getStrike();
final BlackFunctionData data = new BlackFunctionData(spot / df, df, 0.0);
double impVol1;
try {
impVol1 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price1);
} catch (final Exception e) {
impVol1 = 0.0;
}
double impVol2;
try {
impVol2 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price2);
} catch (final Exception e) {
impVol2 = 0.0;
}
// System.out.println(spot + "\t" + price1 + "\t" + price2 + "\t" + impVol1 + "\t" + impVol2);
if (moneyness >= lowerMoneyness && moneyness <= upperMoneyness) {
assertEquals(impVol1, impVol2, 1e-8);
assertEquals(VOL1, impVol1, 2e-3);
}
}
}
/**
* Test to look at the smile produced from the Monte Carlo simulation of the two state Markov chain model
*/
@Test(enabled = false)
public void testMCSmile() {
final double lambda12 = 0.3;//0.2;
final double lambda21 = 4.0;//2.0;
final double df = YIELD_CURVE.getDiscountFactor(T);
final MarkovChain mc = new MarkovChain(VOL1, VOL2, lambda12, lambda21, 1.0);
final double[] mcSims = mc.simulate(T, 1000);
for (int i = 0; i < 101; i++) {
final double strike = 0.003 + 0.18 * i / 100.0;
final BlackFunctionData data = new BlackFunctionData(0.03, 1.0, 0.0);
final EuropeanVanillaOption option = new EuropeanVanillaOption(strike, T, true);
final double price = mc.price(0.03, 1.0, strike, T, mcSims);
double impVol1;
try {
impVol1 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, option, price);
} catch (final Exception e) {
impVol1 = 0.0;
}
System.out.println(strike + "\t" + price + "\t" + impVol1);
}
}
@Test
public void testSmile() {
final CoupledFiniteDifference solver = new CoupledFiniteDifference(0.5, false);
final double lambda12 = 0.2;
final double lambda21 = 2.0;
TwoStateMarkovChainDataBundle chainData = new TwoStateMarkovChainDataBundle(VOL1, VOL2, lambda12, lambda21, 1.0);
ConvectionDiffusionPDE1DCoupledCoefficients[] pdeData = PDE_DATA_PROVIDER.getCoupledBackwardsPair(FORWARD, T, chainData);
final int timeNodes = 20;
final int spaceNodes = 150;
final double lowerMoneyness = 0.3;
final double upperMoneyness = 3.0;
//state in state 1
final MarkovChain mc = new MarkovChain(VOL1, VOL2, lambda12, lambda21, 1.0);
final double[] mcSims = mc.simulate(T, 10000); // simulate the vol path
final MeshingFunction timeMesh = new ExponentialMeshing(0, T, timeNodes, 0);
// MeshingFunction spaceMesh = new HyperbolicMeshing(LOWER.getLevel(), UPPER.getLevel(), OPTION.getStrike(), 0.01, spaceNodes);
final MeshingFunction spaceMesh = new ExponentialMeshing(LOWER.getLevel(), UPPER.getLevel(), spaceNodes, 0.0);
final double[] timeGrid = new double[timeNodes];
for (int n = 0; n < timeNodes; n++) {
timeGrid[n] = timeMesh.evaluate(n);
}
final double[] spaceGrid = new double[spaceNodes];
for (int i = 0; i < spaceNodes; i++) {
spaceGrid[i] = spaceMesh.evaluate(i);
}
final PDEGrid1D grid = new PDEGrid1D(timeGrid, spaceGrid);
final CoupledPDEDataBundle d1 = new CoupledPDEDataBundle(pdeData[0], INITIAL_COND, LOWER, UPPER, grid);
final CoupledPDEDataBundle d2 = new CoupledPDEDataBundle(pdeData[1], INITIAL_COND, LOWER, UPPER, grid);
final PDEResults1D[] res = solver.solve(d1, d2);
final double df = YIELD_CURVE.getDiscountFactor(T);
final int n = res[0].getNumberSpaceNodes();
for (int i = 0; i < n; i++) {
final double spot = res[0].getSpaceValue(i);
final double price1 = res[0].getFunctionValue(i);
final double price2 = res[1].getFunctionValue(i);
final double delta = res[0].getFirstSpatialDerivative(i);
final double gamma = res[0].getSecondSpatialDerivative(i);
final double moneyness = spot / OPTION.getStrike();
final BlackFunctionData data = new BlackFunctionData(spot / df, df, 0.0);
final double mc_price = mc.price(spot / df, df, STRIKE, T, mcSims);
double impVol1;
try {
impVol1 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price1);
} catch (final Exception e) {
impVol1 = 0.0;
}
double impVol2;
try {
impVol2 = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, price2);
} catch (final Exception e) {
impVol2 = 0.0;
}
double impVol_mc;
try {
impVol_mc = BLACK_IMPLIED_VOL.getImpliedVolatility(data, OPTION, mc_price);
} catch (final Exception e) {
impVol_mc = 0.0;
}
// System.out.println(spot + "\t" + price1 + "\t" + price2 + "\t" + impVol1 + "\t" + impVol2 + "\t" + delta + "\t" + gamma);
if (moneyness >= lowerMoneyness && moneyness <= upperMoneyness) {
assertEquals(impVol_mc, impVol1, 1e-2);
}
}
}
}