com.opengamma.strata.pricer.impl.volatility.smile.SmileModelFitter Maven / Gradle / Ivy
/*
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.pricer.impl.volatility.smile;
import java.util.BitSet;
import java.util.function.Function;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.array.DoubleArray;
import com.opengamma.strata.collect.array.DoubleMatrix;
import com.opengamma.strata.math.impl.linearalgebra.DecompositionFactory;
import com.opengamma.strata.math.impl.matrix.MatrixAlgebra;
import com.opengamma.strata.math.impl.matrix.OGMatrixAlgebra;
import com.opengamma.strata.math.impl.minimization.NonLinearParameterTransforms;
import com.opengamma.strata.math.impl.minimization.NonLinearTransformFunction;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResults;
import com.opengamma.strata.math.impl.statistics.leastsquare.LeastSquareResultsWithTransform;
import com.opengamma.strata.math.impl.statistics.leastsquare.NonLinearLeastSquare;
/**
* Smile model fitter.
*
* Attempts to calibrate a smile 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 the data of smile model to be calibrated
*/
public abstract class SmileModelFitter {
private static final MatrixAlgebra MA = new OGMatrixAlgebra();
private static final NonLinearLeastSquare SOLVER = new NonLinearLeastSquare(DecompositionFactory.SV_COMMONS, MA, 1e-12);
private static final Function UNCONSTRAINED = new Function() {
@Override
public Boolean apply(DoubleArray x) {
return true;
}
};
private final VolatilityFunctionProvider model;
private final Function volFunc;
private final Function volAdjointFunc;
private final DoubleArray marketValues;
private final DoubleArray errors;
/**
* Constructs smile model fitter from forward, strikes, time to expiry, implied volatilities and error values.
*
* {@code strikes}, {@code impliedVols} and {@code error} should be the same length and ordered coherently.
*
* @param forward the forward value of the underlying
* @param strikes the 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 the volatility function provider
*/
public SmileModelFitter(
double forward,
DoubleArray strikes,
double timeToExpiry,
DoubleArray impliedVols,
DoubleArray error,
VolatilityFunctionProvider model) {
ArgChecker.notNull(strikes, "strikes");
ArgChecker.notNull(impliedVols, "implied vols");
ArgChecker.notNull(error, "errors");
ArgChecker.notNull(model, "model");
int n = strikes.size();
ArgChecker.isTrue(n == impliedVols.size(), "vols not the same length as strikes");
ArgChecker.isTrue(n == error.size(), "errors not the same length as strikes");
this.marketValues = impliedVols;
this.errors = error;
this.model = model;
this.volFunc = new Function() {
@Override
public DoubleArray apply(DoubleArray x) {
final T data = toSmileModelData(x);
double[] res = new double[n];
for (int i = 0; i < n; ++i) {
res[i] = model.volatility(forward, strikes.get(i), timeToExpiry, data);
}
return DoubleArray.copyOf(res);
}
};
this.volAdjointFunc = new Function() {
@Override
public DoubleMatrix apply(DoubleArray x) {
final T data = toSmileModelData(x);
double[][] resAdj = new double[n][];
for (int i = 0; i < n; ++i) {
DoubleArray deriv = model.volatilityAdjoint(forward, strikes.get(i), timeToExpiry, data).getDerivatives();
resAdj[i] = deriv.subArray(2).toArrayUnsafe();
}
return DoubleMatrix.copyOf(resAdj);
}
};
}
/**
* Solves using the default NonLinearParameterTransforms for the concrete implementation.
*
* This returns {@link LeastSquareResults}.
*
* @param start the first guess at the parameter values
* @return the calibration results
*/
public LeastSquareResultsWithTransform solve(DoubleArray start) {
return solve(start, new BitSet());
}
/**
* Solve using the default NonLinearParameterTransforms for the concrete implementation with some parameters fixed
* to their initial values (indicated by fixed).
*
* This returns {@link LeastSquareResults}.
*
* @param start the first guess at the parameter values
* @param fixed the parameters are fixed
* @return the calibration results
*/
public LeastSquareResultsWithTransform solve(DoubleArray start, BitSet fixed) {
NonLinearParameterTransforms transform = getTransform(start, fixed);
return solve(start, transform);
}
/**
* Solve using a user supplied NonLinearParameterTransforms.
*
* This returns {@link LeastSquareResults}.
*
* @param start the first guess at the parameter values
* @param transform transform from model parameters to fitting parameters, and vice versa
* @return the calibration results
*/
public LeastSquareResultsWithTransform solve(DoubleArray start, NonLinearParameterTransforms transform) {
NonLinearTransformFunction transFunc = new NonLinearTransformFunction(volFunc, volAdjointFunc, transform);
LeastSquareResults solRes = SOLVER.solve(marketValues, errors, transFunc.getFittingFunction(),
transFunc.getFittingJacobian(), transform.transform(start), getConstraintFunction(transform), getMaximumStep());
return new LeastSquareResultsWithTransform(solRes, transform);
}
/**
* Obtains volatility function of the smile model.
*
* The function is defined in {@link VolatilityFunctionProvider}.
*
* @return the function
*/
protected Function getModelValueFunction() {
return volFunc;
}
/**
* Obtains Jacobian function of the smile model.
*
* The function is defined in {@link VolatilityFunctionProvider}.
*
* @return the function
*/
protected Function getModelJacobianFunction() {
return volAdjointFunc;
}
/**
* Obtains the maximum number of iterations.
*
* @return the maximum number.
*/
protected abstract DoubleArray getMaximumStep();
/**
* Obtains the nonlinear transformation of parameters from the initial values.
*
* @param start the initial values
* @return the nonlinear transformation
*/
protected abstract NonLinearParameterTransforms getTransform(DoubleArray start);
/**
* Obtains the nonlinear transformation of parameters from the initial values with some parameters fixed.
*
* @param start the initial values
* @param fixed the parameters are fixed
* @return the nonlinear transformation
*/
protected abstract NonLinearParameterTransforms getTransform(DoubleArray start, BitSet fixed);
/**
* Obtains {@code SmileModelData} instance from the model parameters.
*
* @param modelParameters the model parameters
* @return the smile model data
*/
public abstract T toSmileModelData(DoubleArray modelParameters);
/**
* Obtains the constraint function.
*
* This is defaulted to be "unconstrained".
*
* @param t the nonlinear transformation
* @return the constraint function
*/
protected Function getConstraintFunction(
@SuppressWarnings("unused") final NonLinearParameterTransforms t) {
return UNCONSTRAINED;
}
/**
* Obtains the volatility function provider.
*
* @return the volatility function provider
*/
public VolatilityFunctionProvider getModel() {
return model;
}
}