/** * 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.surface; import static com.opengamma.financial.analytics.model.volatility.surface.SABRFittingProperties.PROPERTY_ERROR; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; 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.curve.NodalDoublesCurve; import com.opengamma.analytics.math.interpolation.Interpolator2D; 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.VolatilitySurfaceData; import com.opengamma.engine.ComputationTarget; 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.target.PrimitiveComputationTargetType; import com.opengamma.engine.value.ComputedValue; import com.opengamma.engine.value.ValueProperties; import com.opengamma.engine.value.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueRequirementNames; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.financial.analytics.model.InstrumentTypeProperties; import com.opengamma.financial.analytics.model.volatility.SmileFittingPropertyNamesAndValues; import com.opengamma.financial.analytics.model.volatility.surface.fitted.SurfaceFittedSmileDataPoints; import com.opengamma.financial.analytics.volatility.fittedresults.SABRFittedSurfaces; import com.opengamma.util.money.Currency; import com.opengamma.util.tuple.DoublesPair; import com.opengamma.util.tuple.ObjectsPair; /** * */ public class SABRNonLinearLeastSquaresIRFutureOptionSurfaceFittingFunction extends AbstractFunction.NonCompiledInvoker { /** A logger */ private static final Logger s_logger = LoggerFactory.getLogger(SABRNonLinearLeastSquaresIRFutureOptionSurfaceFittingFunction.class); /** Hagan SABR function */ private static final SABRHaganVolatilityFunction SABR_FUNCTION = new SABRHaganVolatilityFunction(); @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) { final ValueRequirement desiredValue = desiredValues.iterator().next(); final Object objectSurfaceData = inputs.getValue(ValueRequirementNames.STANDARD_VOLATILITY_SURFACE_DATA); if (objectSurfaceData == null) { throw new OpenGammaRuntimeException("Could not get volatility surface data"); } @SuppressWarnings("unchecked") final VolatilitySurfaceData<Number, Double> volatilitySurfaceData = (VolatilitySurfaceData<Number, Double>) objectSurfaceData; final Object objectFuturePriceData = inputs.getValue(ValueRequirementNames.FUTURE_PRICE_CURVE_DATA); if (objectFuturePriceData == null) { throw new OpenGammaRuntimeException("Could not get futures price data"); } final NodalDoublesCurve futurePriceData = (NodalDoublesCurve) objectFuturePriceData; //assumes that the sorting is first x, then y if (volatilitySurfaceData.size() == 0) { throw new OpenGammaRuntimeException("Interest rate future option volatility surface name" + desiredValue.getConstraint(ValuePropertyNames.SURFACE) + " contains no data"); } final Double error = Double.parseDouble(desiredValue.getConstraint(PROPERTY_ERROR)); final DoubleMatrix1D sabrInitialValues = SABRFittingPropertyUtils.getStartingValues(desiredValue); final BitSet fixed = SABRFittingPropertyUtils.getFixedValues(desiredValue); final Interpolator2D interpolator = SABRFittingPropertyUtils.getInterpolator(desiredValue); final SortedSet<Number> timeValues = volatilitySurfaceData.getUniqueXValues(); final DoubleArrayList fittedOptionExpiryList = new DoubleArrayList(); final DoubleArrayList futureDelayList = 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<DoublesPair, DoubleMatrix2D>(); final Map<Double, List<Double>> dataPointsForStrip = new HashMap<Double, List<Double>>(); for (final Number ttm : timeValues) { final List<Double> fittedPointsForStrip = new ArrayList<Double>(); final List<ObjectsPair<Double, Double>> strip = volatilitySurfaceData.getYValuesForX(ttm); final DoubleArrayList errors = new DoubleArrayList(); final DoubleArrayList strikes = new DoubleArrayList(); final DoubleArrayList blackVols = new DoubleArrayList(); if (strip.size() > 4) { try { final Double forward = futurePriceData.getYValue(ttm.doubleValue()); for (final ObjectsPair<Double, Double> value : strip) { if (value.second != null) { strikes.add(1 - value.first); blackVols.add(value.second); errors.add(error); fittedPointsForStrip.add(value.first); } } if (blackVols.size() > 4) { final LeastSquareResultsWithTransform fittedResult = new SABRModelFitter(forward, strikes.toDoubleArray(), ttm.doubleValue(), blackVols.toDoubleArray(), errors.toDoubleArray(), SABR_FUNCTION).solve(sabrInitialValues, fixed); final DoubleMatrix1D parameters = fittedResult.getModelParameters(); fittedOptionExpiryList.add(ttm.doubleValue()); futureDelayList.add(0); alphaList.add(parameters.getEntry(0)); betaList.add(parameters.getEntry(1)); nuList.add(parameters.getEntry(2)); rhoList.add(parameters.getEntry(3)); inverseJacobians.put(DoublesPair.of(ttm.doubleValue(), 0.), fittedResult.getModelParameterSensitivityToData()); chiSqList.add(fittedResult.getChiSq()); dataPointsForStrip.put(ttm.doubleValue(), fittedPointsForStrip); } } catch (final IllegalArgumentException e) { s_logger.info("Could not get values for forward for x={}", ttm); } } } if (fittedOptionExpiryList.size() < 4) { //don't have sufficient fits to construct a surface throw new OpenGammaRuntimeException("Could not construct SABR parameter surfaces; have less than 3 surface points"); } final double[] fittedOptionExpiry = fittedOptionExpiryList.toDoubleArray(); final double[] futureDelay = futureDelayList.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(fittedOptionExpiry, futureDelay, alpha, interpolator, "SABR alpha surface"); final InterpolatedDoublesSurface betaSurface = InterpolatedDoublesSurface.from(fittedOptionExpiry, futureDelay, beta, interpolator, "SABR beta surface"); final InterpolatedDoublesSurface nuSurface = InterpolatedDoublesSurface.from(fittedOptionExpiry, futureDelay, nu, interpolator, "SABR nu surface"); final InterpolatedDoublesSurface rhoSurface = InterpolatedDoublesSurface.from(fittedOptionExpiry, futureDelay, rho, interpolator, "SABR rho surface"); final SABRFittedSurfaces fittedSurfaces = new SABRFittedSurfaces(alphaSurface, betaSurface, nuSurface, rhoSurface, inverseJacobians); final ValueProperties resultProperties = desiredValue.getConstraints().copy() .withoutAny(ValuePropertyNames.FUNCTION) .with(ValuePropertyNames.FUNCTION, getUniqueId()).get(); final ValueSpecification resultSpecification = new ValueSpecification(ValueRequirementNames.SABR_SURFACES, target.toSpecification(), resultProperties); final ValueSpecification fittedPointsSpecification = new ValueSpecification(ValueRequirementNames.VOLATILITY_SURFACE_FITTED_POINTS, target.toSpecification(), resultProperties); return Sets.newHashSet(new ComputedValue(resultSpecification, fittedSurfaces), new ComputedValue(fittedPointsSpecification, new SurfaceFittedSmileDataPoints(dataPointsForStrip))); } @Override public ComputationTargetType getTargetType() { return ComputationTargetType.CURRENCY; } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) { final Currency currency = target.getValue(PrimitiveComputationTargetType.CURRENCY); final ValueProperties resultProperties = SABRFittingPropertyUtils.addNLSSFittingProperties(createValueProperties()) .with(ValuePropertyNames.CURRENCY, currency.getCode()) .withAny(ValuePropertyNames.SURFACE) .with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, InstrumentTypeProperties.IR_FUTURE_OPTION) .with(SmileFittingPropertyNamesAndValues.PROPERTY_VOLATILITY_MODEL, SmileFittingPropertyNamesAndValues.SABR) .with(SmileFittingPropertyNamesAndValues.PROPERTY_FITTING_METHOD, SmileFittingPropertyNamesAndValues.NON_LINEAR_LEAST_SQUARES).get(); final ValueSpecification resultSpecification = new ValueSpecification(ValueRequirementNames.SABR_SURFACES, target.toSpecification(), resultProperties); final ValueSpecification fittedPointsSpecification = new ValueSpecification(ValueRequirementNames.VOLATILITY_SURFACE_FITTED_POINTS, target.toSpecification(), resultProperties); return Sets.newHashSet(resultSpecification, fittedPointsSpecification); } @Override public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) { final Set<String> surfaceNames = desiredValue.getConstraints().getValues(ValuePropertyNames.SURFACE); if (surfaceNames == null || surfaceNames.size() != 1) { s_logger.error("Need to provide a single surface name; have {}", surfaceNames); return null; } if (!SABRFittingPropertyUtils.ensureNLSSFittingProperties(desiredValue)) { return null; } final String surfaceName = Iterables.getOnlyElement(surfaceNames); final ValueProperties surfaceProperties = ValueProperties.builder() .with(ValuePropertyNames.SURFACE, surfaceName) .with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, InstrumentTypeProperties.IR_FUTURE_OPTION).get(); final ValueProperties futurePriceProperties = ValueProperties.builder() .with(ValuePropertyNames.CURVE, surfaceName) .with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, InstrumentTypeProperties.IR_FUTURE_PRICE).get(); final ValueRequirement surfaceRequirement = new ValueRequirement(ValueRequirementNames.STANDARD_VOLATILITY_SURFACE_DATA, target.toSpecification(), surfaceProperties); final ValueRequirement futurePriceRequirement = new ValueRequirement(ValueRequirementNames.FUTURE_PRICE_CURVE_DATA, target.toSpecification(), futurePriceProperties); return Sets.newHashSet(futurePriceRequirement, surfaceRequirement); } }