/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.smile.function; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.model.option.pricing.analytic.formula.EuropeanVanillaOption; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.analytics.math.matrix.MatrixAlgebra; import com.opengamma.analytics.math.matrix.OGMatrixAlgebra; import com.opengamma.util.ArgumentChecker; /** * * @param <T> Type of the data needed for the volatility function */ public abstract class VolatilityFunctionProvider<T extends SmileModelData> { private static final MatrixAlgebra MA = new OGMatrixAlgebra(); private static final double EPS = 1e-6; /** * Returns a function that, given data of type T, calculates the volatility. * @param option The option, not null * @param forward The forward value of the underlying * @return Returns a function that, given data of type T, calculates the volatility */ public abstract Function1D<T, Double> getVolatilityFunction(final EuropeanVanillaOption option, final double forward); /** * Returns a function that, given data of type T, calculates the volatilities for the given strikes * @param forward the forward * @param strikes set of strikes * @param timeToExpiry time-to-expiry * @return A set of volatilities for the given strikes */ public Function1D<T, double[]> getVolatilityFunction(final double forward, final double[] strikes, final double timeToExpiry) { final int n = strikes.length; final List<Function1D<T, Double>> funcs = new ArrayList<>(n); for (int i = 0; i < n; i++) { final boolean isCall = strikes[i] >= forward; funcs.add(getVolatilityFunction(new EuropeanVanillaOption(strikes[i], timeToExpiry, isCall), forward)); } return new Function1D<T, double[]>() { @Override public double[] evaluate(final T data) { final double[] res = new double[n]; for (int i = 0; i < n; i++) { res[i] = funcs.get(i).evaluate(data); } return res; } }; } /** * Returns a function that calculates volatility and the ajoint (volatility sensitivity to forward, strike and model parameters) * by means of central finite difference - this should be overridden where possible * @param option The option, not null * @param forward The forward value of the underlying * @return Returns a function that, given data of type T, calculates the volatility adjoint */ public Function1D<T, double[]> getVolatilityAdjointFunction(final EuropeanVanillaOption option, final double forward) { Validate.notNull(option, "option"); Validate.isTrue(forward >= 0.0, "forward must be greater than zero"); final Function1D<T, Double> func = getVolatilityFunction(option, forward); return new Function1D<T, double[]>() { @SuppressWarnings("synthetic-access") @Override public final double[] evaluate(final T data) { Validate.notNull(data, "data"); final double[] x = new double[3 + data.getNumberOfParameters()]; //vol, fwd, strike, the model parameters x[0] = func.evaluate(data); x[1] = forwardBar(option, forward, data); x[2] = strikeBar(option, forward, data); System.arraycopy(paramBar(func, data), 0, x, 3, data.getNumberOfParameters()); return x; } }; } /** *Returns a function that calculates the volatility sensitivity to model parameters * by means of central finite difference - this should be overridden where possible * @param option The option, not null * @param forward The forward value of the underlying * @return Returns a function that, given data of type T, calculates the volatility model sensitivity */ public Function1D<T, double[]> getModelAdjointFunction(final EuropeanVanillaOption option, final double forward) { Validate.notNull(option, "option"); Validate.isTrue(forward >= 0.0, "forward must be greater than zero"); final Function1D<T, Double> func = getVolatilityFunction(option, forward); return new Function1D<T, double[]>() { @SuppressWarnings("synthetic-access") @Override public final double[] evaluate(final T data) { Validate.notNull(data, "data"); return paramBar(func, data); } }; } /** * The volatility adjoint set by finite difference, unless this is overloaded * @param forward Forward value of underlying * @param strikes The strikes * @param timeToExpiry Time-toExpiry * @return The first column is is vol at each strike, the second and third are the forward and strike sensitivity at each strike, and the * remaining columns are the model parameter sensitivities are each strike */ public Function1D<T, double[][]> getVolatilityAdjointFunction(final double forward, final double[] strikes, final double timeToExpiry) { final int n = strikes.length; final Function1D<T, double[]> func = getVolatilityFunction(forward, strikes, timeToExpiry); return new Function1D<T, double[][]>() { @SuppressWarnings("synthetic-access") @Override public double[][] evaluate(final T data) { Validate.notNull(data, "data"); final double[][] res = new double[3 + data.getNumberOfParameters()][n]; res[0] = func.evaluate(data); res[1] = forwardBar(strikes, timeToExpiry, forward, data); res[2] = strikeBar(strikes, timeToExpiry, forward, data); final double[][] temp = paramBarSet(func, data); final int m = temp.length; for (int i = 0; i < m; i++) { res[3 + i] = temp[i]; } //now transpose //TODO a transpose that works on double[][]? return MA.getTranspose(new DoubleMatrix2D(res)).getData(); } }; } protected Function1D<T, double[][]> getVolatilityAdjointFunctionByCallingSingleStrikes(final double forward, final double[] strikes, final double timeToExpiry) { final int n = strikes.length; final List<Function1D<T, double[]>> funcs = new ArrayList<>(n); for (int i = 0; i < n; i++) { final boolean isCall = strikes[i] >= forward; funcs.add(getVolatilityAdjointFunction(new EuropeanVanillaOption(strikes[i], timeToExpiry, isCall), forward)); } return new Function1D<T, double[][]>() { @Override public double[][] evaluate(final T data) { final double[][] res = new double[n][]; for (int i = 0; i < n; i++) { res[i] = funcs.get(i).evaluate(data); } return res; } }; } /** * Returns a function that calculates the volatility sensitivity to model parameters for a range of strikes by means of * central finite difference - this should be overridden where possible * @param forward Forward value of underlying * @param strikes The strikes * @param timeToExpiry Time-toExpiry * @return Matrix (i.e. double[][]) of volatility sensitivities to model parameters, where the columns are the model parameter * sensitivities at each strike */ public Function1D<T, double[][]> getModelAdjointFunction(final double forward, final double[] strikes, final double timeToExpiry) { final Function1D<T, double[]> func = getVolatilityFunction(forward, strikes, timeToExpiry); return new Function1D<T, double[][]>() { @SuppressWarnings("synthetic-access") @Override public double[][] evaluate(final T data) { Validate.notNull(data, "data"); final double[][] temp = paramBarSet(func, data); //now transpose //TODO a transpose that works on double[][]? return MA.getTranspose(new DoubleMatrix2D(temp)).getData(); } }; } protected Function1D<T, double[][]> getModelAdjointFunctionByCallingSingleStrikes(final double forward, final double[] strikes, final double timeToExpiry) { final int n = strikes.length; final List<Function1D<T, double[]>> funcs = new ArrayList<>(n); for (int i = 0; i < n; i++) { final boolean isCall = strikes[i] >= forward; funcs.add(getModelAdjointFunction(new EuropeanVanillaOption(strikes[i], timeToExpiry, isCall), forward)); } return new Function1D<T, double[][]>() { @Override public double[][] evaluate(final T data) { final double[][] res = new double[n][]; for (int i = 0; i < n; i++) { res[i] = funcs.get(i).evaluate(data); } return res; } }; } private double forwardBar(final EuropeanVanillaOption option, final double forward, final T data) { final Function1D<T, Double> funcUp = getVolatilityFunction(option, forward + EPS); final Function1D<T, Double> funcDown = getVolatilityFunction(option, forward - EPS); return (funcUp.evaluate(data) - funcDown.evaluate(data)) / 2 / EPS; } private double[] forwardBar(final double[] strikes, final double timeToExpiry, final double forward, final T data) { final int n = strikes.length; final Function1D<T, double[]> funcUp = getVolatilityFunction(forward + EPS, strikes, timeToExpiry); final Function1D<T, double[]> funcDown = getVolatilityFunction(forward - EPS, strikes, timeToExpiry); final double[] res = new double[n]; final double[] up = funcUp.evaluate(data); final double[] down = funcDown.evaluate(data); for (int i = 0; i < n; i++) { res[i] = (up[i] - down[i]) / 2 / EPS; } return res; } private double strikeBar(final EuropeanVanillaOption option, final double forward, final T data) { final Function1D<T, Double> funcUp = getVolatilityFunction(option.withStrike(option.getStrike() + EPS), forward); final Function1D<T, Double> funcDown = getVolatilityFunction(option.withStrike(option.getStrike() - EPS), forward); return (funcUp.evaluate(data) - funcDown.evaluate(data)) / 2 / EPS; } private double[] strikeBar(final double[] strikes, final double timeToExpiry, final double forward, final T data) { final int n = strikes.length; final double[] res = new double[n]; final double[] strikesUp = new double[n]; final double[] strikesDown = new double[n]; for (int i = 0; i < n; i++) { strikesUp[i] = strikes[i] + EPS; } for (int i = 0; i < n; i++) { strikesDown[i] = strikes[i] - EPS; } final Function1D<T, double[]> funcUp = getVolatilityFunction(forward, strikesUp, timeToExpiry); final Function1D<T, double[]> funcDown = getVolatilityFunction(forward, strikesDown, timeToExpiry); final double[] up = funcUp.evaluate(data); final double[] down = funcDown.evaluate(data); for (int i = 0; i < n; i++) { res[i] = (up[i] - down[i]) / 2 / EPS; } return res; } private double[] paramBar(final Function1D<T, Double> func, final T data) { final int n = data.getNumberOfParameters(); final double[] res = new double[n]; for (int i = 0; i < n; i++) { res[i] = paramBar(func, data, i); } return res; } /** * Get the model's sensitivity to its parameters by finite-difference, taking care on the boundary of allowed regions * @param func Function that gives the volatility for a set of model parameters * @param data Model parameters * @param index the index of the model parameter * @return The first derivative of the volatility WRT the parameter given by index */ private double paramBar(final Function1D<T, Double> func, final T data, final int index) { final double mid = data.getParameter(index); final double up = mid + EPS; final double down = mid - EPS; if (data.isAllowed(index, down)) { if (data.isAllowed(index, up)) { final T dUp = (T) data.with(index, up); final T dDown = (T) data.with(index, down); return (func.evaluate(dUp) - func.evaluate(dDown)) / 2 / EPS; } final T dDown = (T) data.with(index, down); return (func.evaluate(data) - func.evaluate(dDown)) / EPS; } ArgumentChecker.isTrue(data.isAllowed(index, up), "No values and index {} = {} are allowed", index, mid); final T dUp = (T) data.with(index, up); return (func.evaluate(dUp) - func.evaluate(data)) / EPS; } private double[][] paramBarSet(final Function1D<T, double[]> func, final T data) { final int n = data.getNumberOfParameters(); final double[][] res = new double[n][]; for (int i = 0; i < n; i++) { res[i] = paramBarSet(func, data, i); } return res; } /** * Get the model's sensitivity to its parameters by finite-difference, taking care on the boundary of allowed regions * @param func Function that gives the volatility for a set of model parameters * @param data Model parameters * @param index the index of the model parameter * @return The first derivative of the volatility WRT the parameter given by index */ private double[] paramBarSet(final Function1D<T, double[]> func, final T data, final int index) { final double mid = data.getParameter(index); final double up = mid + EPS; final double down = mid - EPS; if (data.isAllowed(index, down)) { if (data.isAllowed(index, up)) { final T dUp = (T) data.with(index, up); final T dDown = (T) data.with(index, down); final double[] rUp = func.evaluate(dUp); final double[] rDown = func.evaluate(dDown); final int m = rUp.length; final double[] res = new double[m]; for (int i = 0; i < m; i++) { res[i] = (rUp[i] - rDown[i]) / 2 / EPS; } return res; } final double[] rMid = func.evaluate(data); final double[] rDown = func.evaluate((T) data.with(index, down)); final int m = rMid.length; final double[] res = new double[m]; for (int i = 0; i < m; i++) { res[i] = (rMid[i] - rDown[i]) / 2 / EPS; } return res; } ArgumentChecker.isTrue(data.isAllowed(index, up), "No values and index {} = {} are allowed", index, mid); final double[] rMid = func.evaluate(data); final double[] rUp = func.evaluate((T) data.with(index, up)); final int m = rMid.length; final double[] res = new double[m]; for (int i = 0; i < m; i++) { res[i] = (rUp[i] - rMid[i]) / 2 / EPS; } return res; } }