/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.model.volatility.smile.fitting; import java.util.BitSet; import org.apache.commons.lang.Validate; import com.opengamma.analytics.financial.model.volatility.smile.function.SmileModelData; import com.opengamma.analytics.financial.model.volatility.smile.function.VolatilityFunctionProvider; import com.opengamma.analytics.math.function.Function1D; import com.opengamma.analytics.math.linearalgebra.DecompositionFactory; import com.opengamma.analytics.math.matrix.DoubleMatrix1D; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.analytics.math.matrix.MatrixAlgebra; import com.opengamma.analytics.math.matrix.OGMatrixAlgebra; import com.opengamma.analytics.math.minimization.NonLinearParameterTransforms; import com.opengamma.analytics.math.minimization.NonLinearTransformFunction; import com.opengamma.analytics.math.statistics.leastsquare.LeastSquareResults; import com.opengamma.analytics.math.statistics.leastsquare.LeastSquareResultsWithTransform; import com.opengamma.analytics.math.statistics.leastsquare.NonLinearLeastSquare; /** * * @param <T> The data for the smile model used */ public abstract class SmileModelFitter<T extends SmileModelData> { private static final MatrixAlgebra MA = new OGMatrixAlgebra(); private static final NonLinearLeastSquare SOLVER = new NonLinearLeastSquare(DecompositionFactory.SV_COLT, MA, 1e-12); private static final Function1D<DoubleMatrix1D, Boolean> UNCONSTRAINED = new Function1D<DoubleMatrix1D, Boolean>() { @Override public Boolean evaluate(final DoubleMatrix1D x) { return true; } }; private final VolatilityFunctionProvider<T> _model; private final Function1D<T, double[]> _volFunc; private final Function1D<T, double[][]> _volAdjointFunc; private final DoubleMatrix1D _marketValues; private final DoubleMatrix1D _errors; /** * Attempts to calibrate a model to the implied volatilities of European vanilla options, by minimising the sum of squares between the * market and model implied volatilities. All the options must be for the same expiry and (implicitly) on the same underlying. * @param forward The forward value of the underlying * @param strikes ordered values of strikes * @param timeToExpiry The time-to-expiry * @param impliedVols The market implied volatilities * @param error The 'measurement' error to apply to the market volatility of a particular option TODO: Review should this be part of EuropeanOptionMarketData? * @param model VolatilityFunctionProvider */ public SmileModelFitter(final double forward, final double[] strikes, final double timeToExpiry, final double[] impliedVols, final double[] error, final VolatilityFunctionProvider<T> model) { Validate.notNull(strikes, "null strikes"); Validate.notNull(impliedVols, "null implied vols"); Validate.notNull(error, "null errors"); Validate.notNull(model, "null model"); final int n = strikes.length; Validate.isTrue(n == impliedVols.length, "vols not the same length as strikes"); Validate.isTrue(n == error.length, "errors not the same length as strikes"); _marketValues = new DoubleMatrix1D(impliedVols); _errors = new DoubleMatrix1D(error); _volFunc = model.getVolatilityFunction(forward, strikes, timeToExpiry); _volAdjointFunc = model.getModelAdjointFunction(forward, strikes, timeToExpiry); _model = model; } /** * Solve using the default NonLinearParameterTransforms for the concrete implementation * @param start The first guess at the parameter values * @return The LeastSquareResults */ public LeastSquareResultsWithTransform solve(final DoubleMatrix1D start) { return solve(start, new BitSet()); } /** * Solve using the default NonLinearParameterTransforms for the concrete implementation, but with some parameters fixed to their initial * values (indicated by fixed) * @param start The first guess at the parameter values * @param fixed Indicates which parameters are fixed * @return The LeastSquareResults */ public LeastSquareResultsWithTransform solve(final DoubleMatrix1D start, final BitSet fixed) { final NonLinearParameterTransforms transform = getTransform(start, fixed); return solve(start, transform); } /** * Solve using a user supplied NonLinearParameterTransforms * @param start The first guess at the parameter values * @param transform Transform from model parameters to fitting parameters, and vice versa * @return The LeastSquareResults */ public LeastSquareResultsWithTransform solve(final DoubleMatrix1D start, final NonLinearParameterTransforms transform) { final NonLinearTransformFunction transFunc = new NonLinearTransformFunction(getModelValueFunction(), getModelJacobianFunction(), transform); final LeastSquareResults solRes = SOLVER.solve(_marketValues, _errors, transFunc.getFittingFunction(), transFunc.getFittingJacobian(), transform.transform(start), getConstraintFunction(transform), getMaximumStep()); return new LeastSquareResultsWithTransform(solRes, transform); } protected Function1D<DoubleMatrix1D, DoubleMatrix1D> getModelValueFunction() { return new Function1D<DoubleMatrix1D, DoubleMatrix1D>() { @SuppressWarnings("synthetic-access") @Override public DoubleMatrix1D evaluate(final DoubleMatrix1D x) { final T data = toSmileModelData(x); final double[] res = _volFunc.evaluate(data); return new DoubleMatrix1D(res); } }; } protected Function1D<DoubleMatrix1D, DoubleMatrix2D> getModelJacobianFunction() { return new Function1D<DoubleMatrix1D, DoubleMatrix2D>() { @SuppressWarnings("synthetic-access") @Override public DoubleMatrix2D evaluate(final DoubleMatrix1D x) { final T data = toSmileModelData(x); //this thing will be (#strikes/vols) x (# model Params) final double[][] volAdjoint = _volAdjointFunc.evaluate(data); return new DoubleMatrix2D(volAdjoint); } }; } protected abstract DoubleMatrix1D getMaximumStep(); protected abstract NonLinearParameterTransforms getTransform(final DoubleMatrix1D start); protected abstract NonLinearParameterTransforms getTransform(final DoubleMatrix1D start, final BitSet fixed); public abstract T toSmileModelData(final DoubleMatrix1D modelParameters); protected Function1D<DoubleMatrix1D, Boolean> getConstraintFunction(@SuppressWarnings("unused") final NonLinearParameterTransforms t) { return UNCONSTRAINED; } public VolatilityFunctionProvider<T> getModel() { return _model; } }