/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.volatility.cube;
import static com.opengamma.engine.value.SurfaceAndCubePropertyNames.PROPERTY_CUBE_DEFINITION;
import static com.opengamma.engine.value.SurfaceAndCubePropertyNames.PROPERTY_CUBE_SPECIFICATION;
import static com.opengamma.engine.value.SurfaceAndCubePropertyNames.PROPERTY_CUBE_UNITS;
import static com.opengamma.engine.value.SurfaceAndCubePropertyNames.PROPERTY_SURFACE_DEFINITION;
import static com.opengamma.engine.value.SurfaceAndCubePropertyNames.PROPERTY_SURFACE_SPECIFICATION;
import static com.opengamma.engine.value.ValueRequirementNames.SABR_SURFACES;
import static com.opengamma.engine.value.ValueRequirementNames.STANDARD_VOLATILITY_CUBE_DATA;
import static com.opengamma.engine.value.ValueRequirementNames.SURFACE_DATA;
import static com.opengamma.engine.value.ValueRequirementNames.VOLATILITY_CUBE_FITTED_POINTS;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Period;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.SABRModelFitter;
import com.opengamma.analytics.financial.model.volatility.smile.function.SABRHaganVolatilityFunction;
import com.opengamma.analytics.math.interpolation.FlatExtrapolator1D;
import com.opengamma.analytics.math.interpolation.GridInterpolator2D;
import com.opengamma.analytics.math.interpolation.Interpolator1DFactory;
import com.opengamma.analytics.math.interpolation.LinearInterpolator1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix1D;
import com.opengamma.analytics.math.matrix.DoubleMatrix2D;
import com.opengamma.analytics.math.statistics.leastsquare.LeastSquareResultsWithTransform;
import com.opengamma.analytics.math.surface.InterpolatedDoublesSurface;
import com.opengamma.core.marketdatasnapshot.SurfaceData;
import com.opengamma.core.marketdatasnapshot.VolatilityCubeData;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.AbstractFunction;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.analytics.model.volatility.SmileFittingPropertyNamesAndValues;
import com.opengamma.financial.analytics.model.volatility.cube.fitted.FittedSmileDataPoints;
import com.opengamma.financial.analytics.volatility.VolatilityQuoteUnits;
import com.opengamma.financial.analytics.volatility.fittedresults.SABRFittedSurfaces;
import com.opengamma.util.time.Tenor;
import com.opengamma.util.tuple.DoublesPair;
import com.opengamma.util.tuple.ObjectsPair;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
import com.opengamma.util.tuple.Triple;
/**
*
*/
public class SABRNonLinearLeastSquaresSwaptionCubeFittingFunction extends AbstractFunction.NonCompiledInvoker {
/** The logger */
private static final Logger s_logger = LoggerFactory.getLogger(SABRNonLinearLeastSquaresSwaptionCubeFittingFunction.class);
/** The error on volatility quotes */
private static final double ERROR = 0.0001;
/** The fixed parameters, where 1 means fixed. The order is (alpha, beta, nu, rho) */
private static final BitSet FIXED = new BitSet();
/** The SABR function */
private static final SABRHaganVolatilityFunction SABR_FUNCTION = new SABRHaganVolatilityFunction();
/** The starting point */
private static final DoubleMatrix1D SABR_INITIAL_VALUES = new DoubleMatrix1D(new double[] {0.05, 0.5, 0.7, 0.3 });
/** The parameter surface x and y interpolator */
private static final LinearInterpolator1D LINEAR = (LinearInterpolator1D) Interpolator1DFactory.getInterpolator(Interpolator1DFactory.LINEAR);
/** The parameter surface x and y extrapolator */
private static final FlatExtrapolator1D FLAT = new FlatExtrapolator1D();
/** The surface interpolator */
private static final GridInterpolator2D INTERPOLATOR = new GridInterpolator2D(LINEAR, LINEAR, FLAT, FLAT);
static {
FIXED.set(1);
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target,
final Set<ValueRequirement> desiredValues) {
final ValueProperties properties = desiredValues.iterator().next().getConstraints().copy().get();
final VolatilityCubeData<Tenor, Tenor, Double> volatilityCubeData = (VolatilityCubeData<Tenor, Tenor, Double>) inputs.getValue(STANDARD_VOLATILITY_CUBE_DATA);
final SurfaceData<Tenor, Tenor> forwardSwapSurface = (SurfaceData<Tenor, Tenor>) inputs.getValue(SURFACE_DATA);
final DoubleArrayList swapMaturitiesList = new DoubleArrayList();
final DoubleArrayList swaptionExpiriesList = new DoubleArrayList();
final DoubleArrayList alphaList = new DoubleArrayList();
final DoubleArrayList betaList = new DoubleArrayList();
final DoubleArrayList nuList = new DoubleArrayList();
final DoubleArrayList rhoList = new DoubleArrayList();
final DoubleArrayList chiSqList = new DoubleArrayList();
final Map<DoublesPair, DoubleMatrix2D> inverseJacobians = new HashMap<>();
final Map<Pair<Tenor, Tenor>, Double[]> fittedRelativeStrikes = new HashMap<>();
for (final Tenor expiry : volatilityCubeData.getUniqueXValues()) {
final double swaptionExpiry = getTime(expiry);
for (final Tenor maturity : volatilityCubeData.getUniqueYValues()) {
final double swapMaturity = getTime(maturity);
final double forward = forwardSwapSurface.getValue(expiry, maturity);
if (volatilityCubeData.asMap().containsKey(Triple.of(expiry, maturity, forward))) {
final List<ObjectsPair<Double, Double>> strikeVol = volatilityCubeData.getZValuesForXandY(expiry, maturity);
final int nVols = strikeVol.size();
if (nVols < 4) {
s_logger.info("Smile had less than 4 points for expiry = {} and maturity = {}", expiry, maturity);
continue;
}
final double[] strikes = new double[nVols];
final Double[] strikeCopy = new Double[nVols]; //TODO
final double[] blackVols = new double[nVols];
final double[] errors = new double[nVols];
int i = 0;
for (final ObjectsPair<Double, Double> sv : strikeVol) {
strikes[i] = sv.getFirst();
strikeCopy[i] = sv.getFirst();
blackVols[i] = sv.getSecond();
errors[i++] = ERROR;
}
final LeastSquareResultsWithTransform fittedResult = new SABRModelFitter(forward, strikes, swaptionExpiry, blackVols, errors, SABR_FUNCTION).solve(SABR_INITIAL_VALUES, FIXED);
final DoubleMatrix1D parameters = fittedResult.getModelParameters();
swapMaturitiesList.add(swapMaturity);
swaptionExpiriesList.add(swaptionExpiry);
alphaList.add(parameters.getEntry(0));
betaList.add(parameters.getEntry(1));
rhoList.add(parameters.getEntry(2));
nuList.add(parameters.getEntry(3));
final DoublesPair expiryMaturityPair = DoublesPair.of(swaptionExpiry, swapMaturity);
inverseJacobians.put(expiryMaturityPair, fittedResult.getModelParameterSensitivityToData());
chiSqList.add(fittedResult.getChiSq());
fittedRelativeStrikes.put(Pairs.of(expiry, maturity), strikeCopy);
}
}
}
if (swapMaturitiesList.size() < 5) { //don't have sufficient fits to construct a surface
throw new OpenGammaRuntimeException("Could not construct SABR parameter surfaces; have under 5 surface points");
}
final double[] swapMaturities = swapMaturitiesList.toDoubleArray();
final double[] swaptionExpiries = swaptionExpiriesList.toDoubleArray();
final double[] alpha = alphaList.toDoubleArray();
final double[] beta = betaList.toDoubleArray();
final double[] nu = nuList.toDoubleArray();
final double[] rho = rhoList.toDoubleArray();
final InterpolatedDoublesSurface alphaSurface = InterpolatedDoublesSurface.from(swaptionExpiries, swapMaturities, alpha, INTERPOLATOR, "SABR alpha surface");
final InterpolatedDoublesSurface betaSurface = InterpolatedDoublesSurface.from(swaptionExpiries, swapMaturities, beta, INTERPOLATOR, "SABR beta surface");
final InterpolatedDoublesSurface nuSurface = InterpolatedDoublesSurface.from(swaptionExpiries, swapMaturities, nu, INTERPOLATOR, "SABR nu surface");
final InterpolatedDoublesSurface rhoSurface = InterpolatedDoublesSurface.from(swaptionExpiries, swapMaturities, rho, INTERPOLATOR, "SABR rho surface");
final SABRFittedSurfaces fittedSurfaces = new SABRFittedSurfaces(alphaSurface, betaSurface, nuSurface, rhoSurface, inverseJacobians);
final ValueSpecification sabrSurfacesSpecification = new ValueSpecification(SABR_SURFACES, target.toSpecification(), properties);
final ValueSpecification smileIdsSpecification = new ValueSpecification(VOLATILITY_CUBE_FITTED_POINTS, target.toSpecification(), properties);
return Sets.newHashSet(new ComputedValue(sabrSurfacesSpecification, fittedSurfaces), new ComputedValue(smileIdsSpecification, new FittedSmileDataPoints(fittedRelativeStrikes)));
}
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.NULL;
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
final ValueProperties properties = getResultProperties();
final ValueSpecification sabrSurfacesSpecification = new ValueSpecification(SABR_SURFACES, target.toSpecification(), properties);
final ValueSpecification smileIdsSpecification = new ValueSpecification(VOLATILITY_CUBE_FITTED_POINTS, target.toSpecification(), properties);
return Sets.newHashSet(sabrSurfacesSpecification, smileIdsSpecification);
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final ValueProperties constraints = desiredValue.getConstraints();
final Set<String> cubeDefinitionNames = constraints.getValues(PROPERTY_CUBE_DEFINITION);
if (cubeDefinitionNames == null || cubeDefinitionNames.size() != 1) {
return null;
}
final Set<String> cubeSpecificationNames = constraints.getValues(PROPERTY_CUBE_SPECIFICATION);
if (cubeSpecificationNames == null || cubeSpecificationNames.size() != 1) {
return null;
}
final Set<String> surfaceDefinitionNames = constraints.getValues(PROPERTY_SURFACE_DEFINITION);
if (surfaceDefinitionNames == null || surfaceDefinitionNames.size() != 1) {
return null;
}
final Set<String> surfaceSpecificationNames = constraints.getValues(PROPERTY_SURFACE_SPECIFICATION);
if (surfaceSpecificationNames == null || surfaceSpecificationNames.size() != 1) {
return null;
}
final Set<ValueRequirement> requirements = new HashSet<>();
requirements.add(getCubeDataRequirement(cubeDefinitionNames, cubeSpecificationNames, surfaceDefinitionNames, surfaceSpecificationNames));
requirements.add(getForwardSwapDataRequirement(surfaceDefinitionNames, surfaceSpecificationNames));
return requirements;
}
/**
* Gets the year fraction from a tenor. Assumes twelve months in a year.
* @param tenor The tenor
* @return The year fraction
*/
private static double getTime(final Tenor tenor) {
final Period period = tenor.getPeriod();
final double months = period.toTotalMonths();
return months / 12.;
}
/**
* Constructs the volatility cube requirement.
* @param definitionNames The cube definition name
* @param specificationNames The cube specification name
* @param surfaceDefinitionNames The surface definition name
* @param surfaceSpecificationNames The surface specification name
* @return The volatility cube requirement
*/
private static ValueRequirement getCubeDataRequirement(final Set<String> definitionNames, final Set<String> specificationNames,
final Set<String> surfaceDefinitionNames, final Set<String> surfaceSpecificationNames) {
final ValueProperties cubeProperties = ValueProperties.builder()
.with(PROPERTY_CUBE_DEFINITION, definitionNames)
.with(PROPERTY_CUBE_SPECIFICATION, specificationNames)
.with(PROPERTY_SURFACE_DEFINITION, surfaceDefinitionNames)
.with(PROPERTY_SURFACE_SPECIFICATION, surfaceSpecificationNames)
.with(PROPERTY_CUBE_UNITS, VolatilityQuoteUnits.LOGNORMAL.getName())
.get();
return new ValueRequirement(STANDARD_VOLATILITY_CUBE_DATA, ComputationTargetSpecification.NULL, cubeProperties);
}
/**
* Constructs the forward swap surface requirement.
* @param definitionNames The forward swap surface definition name
* @param specificationNames The forward swap surface definition name
* @return The forward swap surface requirement
*/
private static ValueRequirement getForwardSwapDataRequirement(final Set<String> definitionNames, final Set<String> specificationNames) {
final ValueProperties surfaceProperties = ValueProperties.builder()
.with(PROPERTY_SURFACE_DEFINITION, definitionNames)
.with(PROPERTY_SURFACE_SPECIFICATION, specificationNames)
.get();
return new ValueRequirement(SURFACE_DATA, ComputationTargetSpecification.NULL, surfaceProperties);
}
/**
* Constructs the SABR surface result properties.
* @return The SABR surface result properties
*/
private ValueProperties getResultProperties() {
return createValueProperties()
.withAny(PROPERTY_CUBE_DEFINITION)
.withAny(PROPERTY_CUBE_SPECIFICATION)
.withAny(PROPERTY_SURFACE_DEFINITION)
.withAny(PROPERTY_SURFACE_SPECIFICATION)
.with(SmileFittingPropertyNamesAndValues.PROPERTY_VOLATILITY_MODEL, SmileFittingPropertyNamesAndValues.SABR)
.with(SmileFittingPropertyNamesAndValues.PROPERTY_FITTING_METHOD, SmileFittingPropertyNamesAndValues.NON_LINEAR_LEAST_SQUARES).get();
}
}