/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.finitedifference.applications;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
import com.opengamma.analytics.financial.model.interestrate.curve.DiscountCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve;
import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.CEVFunctionData;
import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.CEVPriceFunction;
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.local.LocalVolatilitySurfaceStrike;
import com.opengamma.analytics.financial.model.volatility.smile.function.MultiHorizonMixedLogNormalModelData;
import com.opengamma.analytics.financial.model.volatility.surface.MixedLogNormalVolatilitySurface;
import com.opengamma.analytics.financial.model.volatility.surface.PriceSurface;
import com.opengamma.analytics.math.curve.ConstantDoublesCurve;
import com.opengamma.analytics.math.curve.Curve;
import com.opengamma.analytics.math.curve.FunctionalDoublesCurve;
import com.opengamma.analytics.math.function.Function;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.function.Function2D;
import com.opengamma.analytics.math.integration.Integrator1D;
import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D;
import com.opengamma.analytics.math.surface.FunctionalDoublesSurface;
import com.opengamma.util.test.TestGroup;
/**
* Test.
*/
@Test(groups = TestGroup.UNIT)
public class LocalVolatilityBackwardsPDEPricerTest {
private static final LocalVolatilityBackwardsPDEPricer PRICER = new LocalVolatilityBackwardsPDEPricer();
private static final ForwardCurve FWD_CURVE;
private static final YieldAndDiscountCurve DIS_CURVE;
private static final Curve<Double, Double> RISK_FREE_CURVE;
private static final LocalVolatilitySurfaceStrike LOCAL_VOL_SUR;
private static final double S0 = 10.0;
private static final double R = 0.07;
private static final double T = 2.0;
private static final double SIGMA_HAT;
static {
final Integrator1D<Double, Double> integrator = new RungeKuttaIntegrator1D();
FWD_CURVE = new ForwardCurve(S0, R);
RISK_FREE_CURVE = ConstantDoublesCurve.from(R);
final Function1D<Double, Double> df = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double t) {
return Math.exp(-t * R);
}
};
DIS_CURVE = new DiscountCurve("discount", FunctionalDoublesCurve.from(df));
final Function1D<Double, Double> volTS = new Function1D<Double, Double>() {
private final static double a = -0.1;
private final static double b = 0.3;
private final static double c = 0.4;
private final static double d = 0.3;
@Override
public Double evaluate(final Double t) {
final double tau = T - t;
return (a + b * tau) * Math.exp(-c * tau) + d;
}
};
final Function1D<Double, Double> vol2TS = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double t) {
final double vol = volTS.evaluate(t);
return vol * vol;
}
};
final Function<Double, Double> vol = new Function2D<Double, Double>() {
@Override
public Double evaluate(final Double t, final Double s) {
return volTS.evaluate(t);
}
};
LOCAL_VOL_SUR = new LocalVolatilitySurfaceStrike(FunctionalDoublesSurface.from(vol));
SIGMA_HAT = Math.sqrt(integrator.integrate(vol2TS, 0.0, T) / T);
}
/**
* Here the vol surface is flat in the spot direction
*/
@Test
public void volTermStructureTest() {
final double k = 14.0;
final boolean isCall = true;
final int tNodes = 150;
int nu = 80;
int xNodes = nu * tNodes;
final double df = DIS_CURVE.getDiscountFactor(T);
final double fwd = FWD_CURVE.getForward(T);
final double bsPrice = df * BlackFormulaRepository.price(fwd, k, T, SIGMA_HAT, isCall);
final EuropeanVanillaOption option = new EuropeanVanillaOption(k, T, isCall);
double pdePrice = PRICER.price(FWD_CURVE, RISK_FREE_CURVE, option, LOCAL_VOL_SUR, isCall, xNodes, tNodes);
// double relErr = Math.abs((pdePrice - bsPrice) / bsPrice);
// System.out.println(bsPrice+"\t"+pdePrice+"\t"+relErr);
assertEquals(bsPrice, pdePrice, 1e-5 * bsPrice);
// with a better setup grid we can use one 20th of the number of nodes (i.e. computation is 20 times faster) for the same accuracy
nu = 4;
xNodes = nu * tNodes;
pdePrice = PRICER.price(FWD_CURVE, RISK_FREE_CURVE, option, LOCAL_VOL_SUR, isCall, xNodes, tNodes, 0.1, 0.0, 5.0);
// relErr = Math.abs((pdePrice - bsPrice) / bsPrice);
// System.out.println(bsPrice+"\t"+pdePrice+"\t"+relErr);
assertEquals(bsPrice, pdePrice, 1e-5 * bsPrice);
}
/**
* Here the vol surface is flat in the time direction
* The dynamics of a CEV process are $df_t = \sigma_{\beta} f_t^{\beta} dW$ where $f$ is the forward ($f(t,T)$) for some expiry $T$.
* For this test we'd like to work with spot rather that forward - for a deterministic rate (and zero yield), the forward and spot are related by
* $f_t = R_t S_t$ where $R_t = \exp(\int_t^T r_s dt)$. The dynamics of spot are $\frac{dS_t}{S_t} = r_t dt + \sigma_{\beta} (R_t S_t)^{\beta-1} dW$.
* This means we can treat the local volatility as $\sigma(t,S_t) = \sigma_{\beta} (R_t S_t)^{\beta-1}$
*/
@Test
public void cevTest() {
final CEVPriceFunction cev = new CEVPriceFunction();
final double k = 14.0;
final boolean isCall = true;
final int tNodes = 100;
int nu = 80;
int xNodes = nu * tNodes;
final double df = DIS_CURVE.getDiscountFactor(T);
final double fwd = FWD_CURVE.getForward(T);
final double sigma = 1.0;
final double beta = 0.5;
final Function<Double, Double> vol = new Function2D<Double, Double>() {
@Override
public Double evaluate(final Double t, final Double s) {
final double tau = T - t;
final double rt = Math.exp(tau * R);
return sigma * Math.pow(rt * s, beta - 1.0);
}
};
final LocalVolatilitySurfaceStrike volSurf = new LocalVolatilitySurfaceStrike(FunctionalDoublesSurface.from(vol));
final EuropeanVanillaOption option = new EuropeanVanillaOption(k, T, isCall);
final CEVFunctionData data = new CEVFunctionData(fwd, df, sigma, beta);
final Function1D<CEVFunctionData, Double> priceFunc = cev.getPriceFunction(option);
final double cevPrice = priceFunc.evaluate(data);
double pdePrice = PRICER.price(FWD_CURVE, RISK_FREE_CURVE, option, volSurf, isCall, xNodes, tNodes);
// double relErr = Math.abs((pdePrice - cevPrice) / cevPrice);
// System.out.println(cevPrice + "\t" + pdePrice + "\t" + relErr);
assertEquals(cevPrice, pdePrice, 1e-5 * cevPrice);
// here only a 5 times speed up is possible
nu = 15;
xNodes = nu * tNodes;
pdePrice = PRICER.price(FWD_CURVE, RISK_FREE_CURVE, option, volSurf, isCall, xNodes, tNodes, 0.1, 0.0, 4.0);
// relErr = Math.abs((pdePrice - cevPrice) / cevPrice);
// System.out.println(cevPrice + "\t" + pdePrice + "\t" + relErr);
assertEquals(cevPrice, pdePrice, 1e-5 * cevPrice);
}
@Test
public void mixedLogNormalTest() {
final double[] w = new double[] {0.7, 0.25, 0.05 };
final double[] sigma = new double[] {0.3, 0.6, 1.0 };
final double[] mu = new double[] {0.0, 0.3, -0.5 };
// double[] w = new double[] {0.99, 0.01, 0.0000};
// double[] sigma = new double[] {0.3, 0.5, 0.8};
// double[] mu = new double[] {0.0, 0.0, -0.0};
final MultiHorizonMixedLogNormalModelData data = new MultiHorizonMixedLogNormalModelData(w, sigma, mu);
final PriceSurface priceSurf = MixedLogNormalVolatilitySurface.getPriceSurface(FWD_CURVE, DIS_CURVE, data);
final LocalVolatilitySurfaceStrike locVol = MixedLogNormalVolatilitySurface.getLocalVolatilitySurface(FWD_CURVE, data);
final double k = 14.0;
final boolean isCall = true;
final int tNodes = 50;
final int nu = 20;
final int xNodes = nu * tNodes;
final EuropeanVanillaOption option = new EuropeanVanillaOption(k, T, isCall);
final double pdePrice = PRICER.price(FWD_CURVE, RISK_FREE_CURVE, option, locVol, isCall, xNodes, tNodes, 0.05, 0.0, 8.0);
final double mlnPrice = priceSurf.getPrice(T, k);
// double relErr = Math.abs((pdePrice - mlnPrice) / mlnPrice);
// System.out.println(mlnPrice + "\t" + pdePrice + "\t" + relErr);
assertEquals(mlnPrice, pdePrice, 5e-3 * mlnPrice);
}
}