CalibratedModel.java
package net.finmath.fouriermethod.calibration;
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Function;
import org.apache.commons.lang3.ArrayUtils;
import net.finmath.exception.CalculationException;
import net.finmath.fouriermethod.calibration.models.CalibratableProcess;
import net.finmath.fouriermethod.models.CharacteristicFunctionModel;
import net.finmath.fouriermethod.products.smile.EuropeanOptionSmile;
import net.finmath.marketdata.model.volatilities.OptionSmileData;
import net.finmath.marketdata.model.volatilities.OptionSurfaceData;
import net.finmath.marketdata.model.volatilities.VolatilitySurface.QuotingConvention;
import net.finmath.optimizer.Optimizer;
import net.finmath.optimizer.OptimizerFactory;
import net.finmath.optimizer.SolverException;
/**
* This class solves a calibration problem. The problem is defined in terms of:
* <ul>
* <li> a generic container of market data OptionSurfaceData.
* <li> a generic pricing model.
* <li> a generic calibration algorithm.
* <li> a generic pricer for claims.
* </ul>
*
* The class supports both calibration in terms of:
* <ul>
* <li> Prices
* <li> Log-normal implied volatilities.
* <li> Normal implied volatilities.
* </ul>
*
* To change the calibration entity please change the convention in the option surface.
* The calibration entity (i.e. price/vol/normal vol) is directly detected from market data.
*
* @author Alessandro Gnoatto
*/
public class CalibratedModel {
private final OptionSurfaceData surface; //target calibration instruments. They dictate the calibration entity: vol/price.
private final CalibratableProcess model; //Pricing model
private final OptimizerFactory optimizerFactory; //construct the instance of the optimization algorithm inside the class.
private final EuropeanOptionSmile pricer; //How do we compute prices: Carr Madan, Cos, Conv, Lewis...
//Optimizer parameters
private final double[] initialParameters;
private final double[] lowerBound;
private final double[] upperBound;
private final double[] parameterStep;
/**
* Create the calibration from data.
*
* @param surface The target calibration instruments. They dictate the calibration entity: vol/price.
* @param model The model to calibrate.
* @param optimizerFactory Factory providing the optimizer to use.
* @param pricer How do we compute prices: Carr Madan, Cos, Conv, Lewis...
* @param initialParameters Initial parameters
* @param parameterStep Parameter steps.
*/
public CalibratedModel(final OptionSurfaceData surface, final CalibratableProcess model,
final OptimizerFactory optimizerFactory, final EuropeanOptionSmile pricer, final double[] initialParameters,
final double[] parameterStep) {
super();
this.surface = surface;
this.model = model;
this.optimizerFactory = optimizerFactory;
this.pricer = pricer;
this.initialParameters = initialParameters;
lowerBound = model.getParameterLowerBounds();
upperBound = model.getParameterUpperBounds();
this.parameterStep = parameterStep;
}
/**
* Solves the calibration problem thus providing a calibrated model.
*
* @return the calibrated model wrapped in an {@link OptimizationResult}.
* @throws SolverException Thrown if the calibration problem cannot be solved.
*/
public OptimizationResult getCalibration() throws SolverException {
final Optimizer.ObjectiveFunction objectiveFunction = new Optimizer.ObjectiveFunction() {
@Override
public void setValues(final double[] parameters, final double[] values) {
//We change the parameters of the model
final CalibratableProcess newModel = model.getCloneForModifiedParameters(parameters);
final CharacteristicFunctionModel newModelFourier = newModel.getCharacteristicFunctionModel();
final int numberOfMaturities = surface.getMaturities().length;
final double[] mats = surface.getMaturities();
final QuotingConvention targetConvention = surface.getQuotingConvention();
final ArrayList<Double> vals = new ArrayList<>();
for(int t = 0; t<numberOfMaturities; t++) {
final double[] currentStrikes = surface.getSmile(mats[t]).getStrikes();
final EuropeanOptionSmile newPricer = pricer.getCloneWithModifiedParameters(mats[t],currentStrikes);
try {
final Map<String, Function<Double, Double>> currentModelPrices = newPricer.getValue(0.0, newModelFourier);
for(int i = 0; i<currentStrikes.length;i++) {
if(targetConvention.equals(QuotingConvention.VOLATILITYLOGNORMAL)) {
//we convert prices into lognormal volatilities
final double forward = surface.getEquityForwardCurve().getDiscountFactor(mats[t]);
final double optionMaturity =mats[t];
final double optionStrike = currentStrikes[i];
final double payoffUnit = surface.getDiscountCurve().getDiscountFactor(mats[t]);
final double optionValue = currentModelPrices.get("valuePerStrike").apply(optionStrike);
vals.add(net.finmath.functions.AnalyticFormulas.blackScholesOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue));
}else if(targetConvention.equals(QuotingConvention.VOLATILITYNORMAL)) {
//we convert prices into normal volatilities
final double forward = surface.getEquityForwardCurve().getDiscountFactor(mats[t]);
final double optionMaturity =mats[t];
final double optionStrike = currentStrikes[i];
final double payoffUnit = surface.getDiscountCurve().getDiscountFactor(mats[t]);
final double optionValue = currentModelPrices.get("valuePerStrike").apply(optionStrike);
vals.add(net.finmath.functions.AnalyticFormulas.bachelierOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue));
}else {
//just output the prices
vals.add(currentModelPrices.get("valuePerStrike").apply(currentStrikes[i]));
}
}
} catch (final CalculationException e) {
e.printStackTrace();
}
}
for(int i = 0; i<values.length; i++) {values[i] = vals.get(i);}
}
};
final Optimizer optimizer = optimizerFactory.getOptimizer(
objectiveFunction,
initialParameters,
lowerBound,
upperBound,
parameterStep,
formatTargetValuesForOptimizer() /* targetValues */);
optimizer.run();
final ArrayList<String> calibrationOutput = outputCalibrationResult(optimizer.getBestFitParameters());
final CalibratableProcess calibratedModel = model.getCloneForModifiedParameters(optimizer.getBestFitParameters());
return new OptimizationResult(calibratedModel, optimizer.getBestFitParameters(), optimizer.getIterations(), optimizer.getRootMeanSquaredError(), calibrationOutput);
}
/**
* This is a service method that takes care of putting al the target values in a single array.
* @return
*/
private double[] formatTargetValuesForOptimizer() {
//Put all values in an array for the optimizer.
final int numberOfMaturities = surface.getMaturities().length;
final double[] mats = surface.getMaturities();
final ArrayList<Double> vals = new ArrayList<>();
for(int t = 0; t<numberOfMaturities; t++) {
final double mat = mats[t];
final double[] myStrikes = surface.getSurface().get(mat).getStrikes();
final OptionSmileData smileOfInterest = surface.getSurface().get(mat);
for(int k = 0; k < myStrikes.length; k++) {
vals.add(smileOfInterest.getSmile().get(myStrikes[k]).getValue());
}
}
final Double[] targetVals = new Double[vals.size()];
return ArrayUtils.toPrimitive(vals.toArray(targetVals));
}
/**
* When the calibration is done this method is called to produce a table
*
* @param parameters Calibration parameters.
*/
private ArrayList<String> outputCalibrationResult(final double[] parameters) {
final ArrayList<String> calibrationOutput = new ArrayList<>();
//We change the parameters of the model
final CalibratableProcess newModel = model.getCloneForModifiedParameters(parameters);
final CharacteristicFunctionModel newModelFourier = newModel.getCharacteristicFunctionModel();
final int numberOfMaturities = surface.getMaturities().length;
final double[] mats = surface.getMaturities();
final QuotingConvention targetConvention = surface.getQuotingConvention();
double value;
double targetValue;
double T;
double K;
calibrationOutput.add("Strike"+ "\t" + "Maturity"+ "\t" + "Market Value" + "\t" + "Model Value" + "\t" + "Squared Error");
for(int t = 0; t<numberOfMaturities; t++) {
T = mats[t];
final OptionSmileData currentSmile = surface.getSmile(mats[t]);
final double[] currentStrikes = currentSmile.getStrikes();
final EuropeanOptionSmile newPricer = pricer.getCloneWithModifiedParameters(mats[t],currentStrikes);
try {
final Map<String, Function<Double, Double>> currentModelPrices = newPricer.getValue(0.0, newModelFourier);
for(int i = 0; i<currentStrikes.length;i++) {
K = currentStrikes[i];
targetValue = currentSmile.getOption(currentStrikes[i]).getValue();
if(targetConvention.equals(QuotingConvention.VOLATILITYLOGNORMAL)) {
//we convert prices into lognormal volatilities
final double forward = surface.getEquityForwardCurve().getDiscountFactor(mats[t]);
final double optionMaturity =mats[t];
final double optionStrike = currentStrikes[i];
final double payoffUnit = surface.getDiscountCurve().getDiscountFactor(mats[t]);
final double optionValue = currentModelPrices.get("valuePerStrike").apply(optionStrike);//currentModelPrices.get(currentStrikes[i]);
value = net.finmath.functions.AnalyticFormulas.blackScholesOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue);
}else if(targetConvention.equals(QuotingConvention.VOLATILITYNORMAL)) {
//we convert prices into normal volatilities
final double forward = surface.getEquityForwardCurve().getDiscountFactor(mats[t]);
final double optionMaturity =mats[t];
final double optionStrike = currentStrikes[i];
final double payoffUnit = surface.getDiscountCurve().getDiscountFactor(mats[t]);
final double optionValue = currentModelPrices.get("valuePerStrike").apply(optionStrike);//currentModelPrices.get(currentStrikes[i]);
value = net.finmath.functions.AnalyticFormulas.bachelierOptionImpliedVolatility(forward, optionMaturity, optionStrike, payoffUnit, optionValue);
}else {
//just output the prices
value = currentModelPrices.get("valuePerStrike").apply(currentStrikes[i]);//currentModelPrices.get(currentStrikes[i]);
}
calibrationOutput.add(K+ "\t" + T + "\t" + targetValue + "\t" + value+ "\t" + Math.pow(targetValue-value,2));
}
} catch (final CalculationException e) {
e.printStackTrace();
}
}
return calibrationOutput;
}
/**
* Helper class for calibration results.
*
* @author Alessandro Gnoatto
*/
public static class OptimizationResult{
private final CalibratableProcess model; //the calibrated model
private final double[] bestFitParameters;
private final int iterations;
private final double rootMeanSquaredError;
private final ArrayList<String> calibrationOutput;
public OptimizationResult(final CalibratableProcess model, final double[] bestFitParameters,
final int iterations, final double rootMeanSquaredError, final ArrayList<String> calibrationOutput) {
super();
this.model = model;
this.bestFitParameters = bestFitParameters;
this.iterations = iterations;
this.rootMeanSquaredError = rootMeanSquaredError;
this.calibrationOutput = calibrationOutput;
}
public CalibratableProcess getModel() {
return model;
}
public double[] getBestFitParameters() {
return bestFitParameters;
}
public int getIterations() {
return iterations;
}
public double getRootMeanSquaredError() {
return rootMeanSquaredError;
}
public ArrayList<String> getCalibrationOutput(){
return calibrationOutput;
}
}
}