/** * 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 java.util.Arrays; import org.testng.annotations.Test; import com.opengamma.analytics.financial.model.finitedifference.applications.PDEUtilityTools; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.math.function.Function; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.integration.Integrator1D; import com.opengamma.analytics.math.integration.RungeKuttaIntegrator1D; import com.opengamma.analytics.math.surface.FunctionalDoublesSurface; import com.opengamma.analytics.math.surface.Surface; import com.opengamma.util.test.TestGroup; /** * The stock price, S_t, is given by S_t = (F_t-D_t)X_t + D_t where F_t is the forward, D_t is the discounted value of future dividend payments * and X_t is the "pure stock price" process. See Buehler, Hans. Volatility and Dividends */ @Test(groups = TestGroup.UNIT) public class PureStockPriceImpliedVolTest { private static final Integrator1D<Double, Double> DEFAULT_INTEGRATOR = new RungeKuttaIntegrator1D(); private static final double SPOT = 100.; private static final double RISK_FREE_RATE = 0.04; private static final double PURE_STOCK_VOL = 0.4; private static int N_DIVS = 10; private static final double[] TAU = new double[N_DIVS]; private static final double[] ALPHA = new double[N_DIVS]; private static final double[] BETA = new double[N_DIVS]; private static final Function1D<Double, Double> R; private static final Function1D<Double, Double> D; private static final Function1D<Double, Double> F; private static final Surface<Double, Double, Double> OTM_PRICE_SURFACE; static { for (int i = 0; i < N_DIVS; i++) { double t = 0.1 + 0.5 * i; TAU[i] = t; } // Arrays.fill(ALPHA, 0.1); Arrays.fill(ALPHA, 0, N_DIVS, 1.0); Arrays.fill(BETA, 0, N_DIVS, 0.01); // for (int i = 5; i < 10; i++) { // ALPHA[i] = 2.0 * (9. - i) / 5.; // BETA[i] = 0.02 * (i - 5.) / 5.; // } // Arrays.fill(BETA, 10, N_DIVS, 0.02); R = new Function1D<Double, Double>() { @Override public Double evaluate(Double t) { int index = 0; double prod = Math.exp(t * RISK_FREE_RATE); while (index < N_DIVS && t >= TAU[index]) { prod *= (1 - BETA[index]); index++; } return prod; } }; D = new Function1D<Double, Double>() { @Override public Double evaluate(Double t) { final double r_t = R.evaluate(t); double sum = 0.0; for (int index = 0; index < N_DIVS; index++) { if (TAU[index] > t) { sum += ALPHA[index] / R.evaluate(TAU[index]); } } return sum * r_t; } }; F = new Function1D<Double, Double>() { @Override public Double evaluate(Double t) { final double r_t = R.evaluate(t); double sum = 0.0; for (int index = 0; index < N_DIVS; index++) { if (TAU[index] <= t) { sum += ALPHA[index] / R.evaluate(TAU[index]); } } return r_t * (SPOT - sum); } }; Function<Double, Double> price = new Function<Double, Double>() { @Override public Double evaluate(Double... tk) { final double t = tk[0]; final double k = tk[1]; final double f = F.evaluate(t); final double d = D.evaluate(t); final boolean isCall = k > f; final double x = (k - d) / (f - d); final double result = BlackFormulaRepository.price(1.0, x, t, PURE_STOCK_VOL, isCall) * (f - d); return result; } }; OTM_PRICE_SURFACE = FunctionalDoublesSurface.from(price); } @Test(enabled = false) public void printForward() { System.out.println("PureStockPriceImpliedVolTest.printForward"); for (int i = 0; i < 101; i++) { double t = 0.7 * i / 100.; double f = F.evaluate(t); double d = D.evaluate(t); double r = R.evaluate(t); System.out.println(t + "\t" + f + "\t" + d + "\t" + r); } } @Test(enabled = false) public void testFlatImpledVol() { System.out.println("PureStockPriceImpliedVolTest.testFlatImpledVol"); final double impVol = 0.4; final Function<Double, Double> pureImpVolFunc = new Function<Double, Double>() { @Override public Double evaluate(Double... tx) { final double t = tx[0]; final double x = tx[1]; final boolean isCall = x > 1.0; final double f = F.evaluate(t); final double d = D.evaluate(t); final double k = (f - d) * x + d; final double price = BlackFormulaRepository.price(f, k, t, impVol, isCall) / (f - d); return BlackFormulaRepository.impliedVolatility(price, 1.0, x, t, isCall); } }; final Surface<Double, Double, Double> pureImpVolSurface = FunctionalDoublesSurface.from(pureImpVolFunc); PDEUtilityTools.printSurface("pure implied vol", pureImpVolSurface, 0.01, 2.0, 0.5, 2.0); } @Test(enabled = false) public void testFlatPureImpledVol() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double pureImpVol = 0.4; final Function<Double, Double> impVolFunc = new Function<Double, Double>() { @Override public Double evaluate(Double... tk) { final double t = tk[0]; final double k = tk[1]; final double f = F.evaluate(t); final double d = D.evaluate(t); final boolean isCall = k > f; final double x = (k - d) / (f - d); final double price = BlackFormulaRepository.price(1.0, x, t, pureImpVol, isCall) * (f - d); return BlackFormulaRepository.impliedVolatility(price, f, k, t, isCall); } }; final Surface<Double, Double, Double> impVolSurface = FunctionalDoublesSurface.from(impVolFunc); PDEUtilityTools.printSurface("implied vol", impVolSurface, 0.01, 2.0, 30, 200); } @Test(enabled = false) public void testFlatPureImpledVol2() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double pureImpVol = 0.4; final Function<Double, Double> impVolFunc = new Function<Double, Double>() { @Override public Double evaluate(Double... tx) { final double t = tx[0]; final double k = tx[1]; final double f = F.evaluate(t); final double d = D.evaluate(t); return (k - d) / k * pureImpVol; } }; final Surface<Double, Double, Double> impVolSurface = FunctionalDoublesSurface.from(impVolFunc); PDEUtilityTools.printSurface("implied vol", impVolSurface, 0.01, 0.2, 20, 200); } @Test(enabled = false) public void calandarSpreadTest() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double pureImpVol = 0.4; final double kM = 110; final double t = 0.1; final double dt = 1e-12; //before final double fM = F.evaluate(t - dt); final double dM = D.evaluate(t - dt); final double xM = (kM - dM) / (fM - dM); final double ppM = BlackFormulaRepository.price(1.0, xM, t - dt, pureImpVol, true); final double pM = (fM - dM) * ppM; final double pivM = BlackFormulaRepository.impliedVolatility(ppM, 1.0, xM, t - dt, true); final double ivM = BlackFormulaRepository.impliedVolatility(pM, fM, kM, t - dt, true); //after final double k = (1 - BETA[0]) * kM - ALPHA[0]; final double f = F.evaluate(t); final double d = D.evaluate(t); final double x = (k - d) / (f - d); final double x2 = (kM - d) / (f - d); System.out.println("x: " + x + "\t" + x2); final double pp = BlackFormulaRepository.price(1.0, x, t, pureImpVol, true); final double p = pp * (f - d); final double piv = BlackFormulaRepository.impliedVolatility(pp, 1.0, x, t, true); final double iv = BlackFormulaRepository.impliedVolatility(p, f, k, t, true); System.out.println(fM + "\t" + f + "|\t" + ppM + "\t" + pp + "|\t" + (1 - BETA[0]) * pM + "\t" + p + "|\t" + pivM + "\t" + piv + "|\t" + ivM + "\t" + iv); final double delta = BlackFormulaRepository.delta(fM, kM, t - dt, 0.37, true); System.out.println(delta); final double dd = BlackFormulaRepository.dualDelta(1.0, xM, t - dt, pureImpVol, true); final double corrDelta = ppM / (fM - dM) - xM * dd; System.out.println(corrDelta); } @Test(enabled = false) public void calandarSpreadTest2() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double impVol = 0.4; final double kM = 110; final double t = 0.1; final double dt = 1e-12; //before final double fM = F.evaluate(t - dt); final double dM = D.evaluate(t - dt); final double xM = (kM - dM) / (fM - dM); final double pM = BlackFormulaRepository.price(fM, kM, t - dt, impVol, true); final double ppM = pM / (fM - dM); final double ivM = BlackFormulaRepository.impliedVolatility(pM, fM, kM, t - dt, true); final double pivM = BlackFormulaRepository.impliedVolatility(ppM, 1.0, xM, t - dt, true); //after final double k = (1 - BETA[0]) * kM - ALPHA[0]; final double f = F.evaluate(t); final double d = D.evaluate(t); final double x = (kM - d) / (f - d); final double p = BlackFormulaRepository.price(f, k, t, impVol, true); final double pp = p / (f - d); final double iv = BlackFormulaRepository.impliedVolatility(p, f, k, t, true); final double piv = BlackFormulaRepository.impliedVolatility(pp, 1.0, x, t, true); System.out.println(fM + "\t" + f + "|\t" + ppM + "\t" + pp + "|\t" + (1 - BETA[0]) * pM + "\t" + p + "|\t" + pivM + "\t" + piv + "|\t" + ivM + "\t" + iv); final double delta = BlackFormulaRepository.delta(fM, kM, t - dt, impVol, true); System.out.println(delta); } @Test(enabled = false) public void calandarSpreadTest3() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double t = 0.1; final double dt = 1e-12; final double p = 0.2; final double pM = p / (1 - BETA[0]); final double kM = 70; final double k = (1 - BETA[0]) * kM - ALPHA[0]; final double fM = F.evaluate(t - dt); final double f = F.evaluate(t); final double ivM = BlackFormulaRepository.impliedVolatility(pM, fM, kM, t - dt, false); final double iv = BlackFormulaRepository.impliedVolatility(p, f, k, t, false); System.out.println(ivM + "\t" + iv); } @Test(enabled = false) public void testRoundTrip() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double impVol = 0.4; final Function<Double, Double> pureImpVolFunc = new Function<Double, Double>() { @Override public Double evaluate(Double... tx) { final double t = tx[0]; final double x = tx[1]; final boolean isCall = x > 1.0; final double f = F.evaluate(t); final double d = D.evaluate(t); final double k = (f - d) * x + d; final double price = BlackFormulaRepository.price(f, k, t, impVol, isCall) / (f - d); return BlackFormulaRepository.impliedVolatility(price, 1.0, x, t, isCall); } }; final Function<Double, Double> impVolFunc = new Function<Double, Double>() { @Override public Double evaluate(Double... tk) { final double t = tk[0]; final double k = tk[1]; final double f = F.evaluate(t); final double d = D.evaluate(t); final boolean isCall = k > f; final double x = (k - d) / (f - d); final double price = BlackFormulaRepository.price(1.0, x, t, pureImpVolFunc.evaluate(t, x), isCall) * (f - d); return BlackFormulaRepository.impliedVolatility(price, f, k, t, isCall); } }; final Surface<Double, Double, Double> impVolSurface = FunctionalDoublesSurface.from(impVolFunc); PDEUtilityTools.printSurface("implied vol", impVolSurface, 0.01, 3.0, 50.0, 200.0); } @Test(enabled = false) public void varianceSwapTest() { System.out.println("PureStockPriceImpliedVolTest.testFlatPureImpledVol"); final double expiry = 0.11; final Function1D<Double, Double> integral = new Function1D<Double, Double>() { @Override public Double evaluate(Double k) { final double price = OTM_PRICE_SURFACE.getZValue(expiry, k); return price / k / k; } }; final double f = F.evaluate(expiry); double var = DEFAULT_INTEGRATOR.integrate(integral, 0.01 * f, 10.0 * f); int index = 0; while (index < N_DIVS && TAU[index] <= expiry) { double temp = correction(index); System.out.println("correction " + index + " " + temp + " " + var); var -= temp; index++; } var *= 2 / expiry; System.out.println("k:" + Math.sqrt(var)); } private double correction(final int index) { final double expiry = TAU[index]; final Function1D<Double, Double> integral = new Function1D<Double, Double>() { @Override public Double evaluate(Double k) { final double price = OTM_PRICE_SURFACE.getZValue(expiry, k); final double dPP = dPPrime(k, index); return price * dPP; } }; double res = DEFAULT_INTEGRATOR.integrate(integral, 0.1, 1000.0); double f = F.evaluate(expiry); res += d(f, index); return res; } private double h(final double x, final int index) { final double a = ALPHA[index]; final double b = BETA[index]; return (x * b + a) / (x + a); } private double hPrime(final double x, final int index) { final double a = ALPHA[index]; final double b = BETA[index]; final double temp = x + a; return a * (b - 1) / temp / temp; } private double hPPrime(final double x, final int index) { final double a = ALPHA[index]; final double b = BETA[index]; final double temp = x + a; return 2 * a * (1 - b) / temp / temp / temp; } private double d(final double x, final int index) { final double a = ALPHA[index]; final double b = BETA[index]; final double h = h(x, index); return Math.log(x * (1 - b) / (x + a)) + h - 0.5 * h * h; } private double dPPrime(final double x, final int index) { final double h = h(x, index); final double hPrime = hPrime(x, index); final double hPPrime = hPPrime(x, index); final double temp = x + ALPHA[index]; double res = -1 / x / x + 1 / temp / temp + (1 - 2 * h) * hPPrime - 2 * hPrime * hPrime; return res; } }