/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.analytics.financial.model.option.pricing.fourier;
import static org.testng.AssertJUnit.assertEquals;
import org.testng.annotations.Test;
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.function.Function1D;
import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.interpolation.data.Interpolator1DDataBundle;
import com.opengamma.analytics.math.number.ComplexNumber;
import com.opengamma.util.test.TestGroup;
/**
* Test.
*/
@Test(groups = TestGroup.UNIT)
public class FFTPricerTest {
private static final Interpolator1D INTERPOLATOR = Interpolator1DFactory.getInterpolator("DoubleQuadratic");
private static final double FORWARD = 100;
private static final double T = 1 / 52.0;
private static final double DF = 0.96;
private static final double SIGMA = 0.2;
private static final BlackFunctionData DATA = new BlackFunctionData(FORWARD, DF, SIGMA);
private static final BlackImpliedVolatilityFormula BLACK_IMPLIED_VOL = new BlackImpliedVolatilityFormula();
private static final MartingaleCharacteristicExponent CEF = new GaussianMartingaleCharacteristicExponent(SIGMA);
private static final FFTPricer PRICER = new FFTPricer();
private static final double ALPHA = -0.5;
private static final double TOL = 1e-8;
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNullCharacteristicExponent1() {
PRICER.price(FORWARD, DF, T, true, null, 10, 10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeTolerance1() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, SIGMA, ALPHA, -TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeNStrikes() {
PRICER.price(FORWARD, DF, T, true, CEF, -10, 10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeMaxDeltaMoneyness() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, -10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeVol1() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, -0.3, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZeroAlpha1() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, SIGMA, 0, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNullCharacteristicExponent2() {
PRICER.price(FORWARD, DF, T, true, null, 10, 110, 10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeTolerance2() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 110, 10, SIGMA, ALPHA, -TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeVol2() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 110, 10, -0.5, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZeroAlpha2() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 110, 10, SIGMA, 0, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testWrongLowStrike() {
PRICER.price(FORWARD, DF, T, true, CEF, FORWARD + 11, 110, 10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testWrongHighStrike() {
PRICER.price(FORWARD, DF, T, true, CEF, FORWARD, FORWARD-1, 10, SIGMA, ALPHA, TOL);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNullCharacteristicExponent3() {
PRICER.price(FORWARD, DF, T, true, null, 10, 10, ALPHA, 0.5, 64, 20);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testZeroAlpha3() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, 0.0, 0.5, 64, 20);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeStrikesAboveATM() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, -10, ALPHA, 0.5, 64, 20);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeStrikesBelowATM() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, -10, ALPHA, 0.5, 64, 20);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeDelta() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, ALPHA, -0.5, 64, 20);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testNegativeM() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, ALPHA, 0.5, 64, -10);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void testWrongN() {
PRICER.price(FORWARD, DF, T, true, CEF, 10, 10, ALPHA, 0.5, 64, 128);
}
@Test
public void testLargeNumberOfStrikes() {
final boolean isCall = true;
final int n = 1024;
final int m = 100;
final double delta = 0.1;
final int nL = 550;
final int nH = 600;
final double alpha = -0.5;
final double[][] temp = PRICER.price(FORWARD, DF, T, isCall, CEF, nL, nH, alpha, delta, n, m);
assertEquals(n + 1, temp.length);
}
@Test
public void test() {
final boolean isCall = true;
final int nStrikes = 21;
final double deltaMoneyness = 0.01;
final double alpha = -0.5;
final double tol = 1e-10;
final double[][] strikeNprice = PRICER.price(FORWARD, DF, T, isCall, CEF, nStrikes, deltaMoneyness, SIGMA, alpha, tol);
assertEquals(nStrikes, strikeNprice.length);
double k;
double price;
for (int i = 0; i < nStrikes; i++) {
price = strikeNprice[i][1];
k = strikeNprice[i][0];
double impVol = 0;
try {
final EuropeanVanillaOption o = new EuropeanVanillaOption(k, T, true);
impVol = BLACK_IMPLIED_VOL.getImpliedVolatility(DATA, o, price);
} catch (final Exception e) {
}
assertEquals(SIGMA, impVol, 1e-5);
}
}
@Test
public void testPutsAndCalls() {
boolean isCall = true;
final double tol = 1e-10;
for (int i = 0; i < 5; i++) {
final double alpha = -1.6 + i * 0.5;
for (int j = 0; j < 1; j++) {
isCall = (j == 0);
final EuropeanVanillaOption option = new EuropeanVanillaOption(FORWARD, T, isCall);
final double[][] strikeNprice = PRICER.price(FORWARD, DF, T, isCall, CEF, FORWARD, FORWARD, 1, 0.3, alpha, tol);
assertEquals(FORWARD, strikeNprice[0][0], 1e-9);
assertEquals(SIGMA, BLACK_IMPLIED_VOL.getImpliedVolatility(DATA, option, strikeNprice[0][1]), 1e-7);
}
}
}
/**
* This test that the same price is produced when the alpha, tolerance and limitSigma are changed
*/
@Test
public void testStability() {
final boolean isCall = false;
final int nStrikes = 21;
final double kappa = 1.2;
final double theta = 0.1;
final double vol0 = 1.8 * theta;
final double omega = 0.4;
final double rho = -0.7;
final double t = 2.0;
final MartingaleCharacteristicExponent heston = new HestonCharacteristicExponent(kappa, theta, vol0, omega, rho);
final double[][] strikeNPrice = PRICER.price(FORWARD, DF, t, isCall, heston, 0.7 * FORWARD, 1.5 * FORWARD, nStrikes, 0.3, -0.5, 1e-10);
final int n = strikeNPrice.length;
final double[] k = new double[n];
final double[] vol = new double[n];
for (int i = 0; i < n; i++) {
k[i] = strikeNPrice[i][0];
vol[i] = BLACK_IMPLIED_VOL.getImpliedVolatility(new BlackFunctionData(FORWARD, DF, 0.0), new EuropeanVanillaOption(k[i], t, isCall), strikeNPrice[i][1]);
}
final Interpolator1DDataBundle dataBundle = INTERPOLATOR.getDataBundleFromSortedArrays(k, vol);
final double[][] strikeNPrice2 = PRICER.price(FORWARD, DF, t, isCall, heston, 0.7 * FORWARD, 1.5 * FORWARD, nStrikes, 0.2, 0.75, 1e-8);
final int m = strikeNPrice2.length;
for (int i = 0; i < m; i++) {
final double strike = strikeNPrice2[i][0];
final double sigma = BLACK_IMPLIED_VOL.getImpliedVolatility(new BlackFunctionData(FORWARD, DF, 0.0), new EuropeanVanillaOption(strike, t, isCall), strikeNPrice2[i][1]);
assertEquals(sigma, INTERPOLATOR.interpolate(dataBundle, strike), 1e-5);
}
}
@Test
public void testDirect() {
final double alpha = 0.5;
final EuropeanCallFourierTransform callFT = new EuropeanCallFourierTransform(CEF);
final Function1D<ComplexNumber, ComplexNumber> callFunction = callFT.getFunction(T);
final Function1D<Double, Double> f = new Function1D<Double, Double>() {
@Override
public Double evaluate(final Double x) {
final ComplexNumber z = new ComplexNumber(x, -1.0 - alpha);
final ComplexNumber u = callFunction.evaluate(z);
return u.getReal();
}
};
final RungeKuttaIntegrator1D integrator = new RungeKuttaIntegrator1D();
final double integral = integrator.integrate(f, 0.0, 1000.0) / Math.PI;
final double price = DF * FORWARD * integral;
double impVol = 0;
try {
final EuropeanVanillaOption option = new EuropeanVanillaOption(FORWARD, T, true);
final BlackFunctionData data = new BlackFunctionData(FORWARD, DF, 0);
impVol = BLACK_IMPLIED_VOL.getImpliedVolatility(data, option, price);
} catch (final Exception e) {
}
assertEquals(SIGMA, impVol, 1e-5);
}
@Test
public void testEuropeanCallFT() {
final Function1D<ComplexNumber, ComplexNumber> callFT = new EuropeanCallFourierTransform(CEF).getFunction(T);
for (int i = 0; i < 101; i++) {
final double x = -10.0 + 20.0 * i / 100;
final ComplexNumber z = new ComplexNumber(x, -1.5);
@SuppressWarnings("unused")
final ComplexNumber u = callFT.evaluate(z);
//System.out.println(x + "\t" + u.getReal() + "\t" + u.getImaginary());
}
}
}