/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.equity.option;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.Validate;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.equity.EquityOptionBlackPresentValueCalculator;
import com.opengamma.analytics.financial.equity.StaticReplicationDataBundle;
import com.opengamma.analytics.financial.equity.option.EquityIndexOption;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.GeneralSmileInterpolator;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.interpolation.SurfaceArrayUtils;
import com.opengamma.analytics.financial.model.volatility.smile.fitting.sabr.SmileSurfaceDataBundle;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneynessFcnBackedByGrid;
import com.opengamma.analytics.financial.model.volatility.surface.VolatilitySurfaceInterpolator;
import com.opengamma.analytics.math.function.Function1D;
import com.opengamma.analytics.math.surface.NodalDoublesSurface;
import com.opengamma.core.security.SecuritySource;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueRequirementNames;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.OpenGammaCompilationContext;
import com.opengamma.financial.analytics.DoubleLabelledMatrix2D;
import com.opengamma.financial.analytics.model.InstrumentTypeProperties;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.FinancialSecurityUtils;
import com.opengamma.id.ExternalId;
import com.opengamma.util.tuple.Triple;
/**
*
*/
public class EquityVanillaBarrierOptionVegaMatrixFunction extends EquityVanillaBarrierOptionBlackFunction {
/** The calculator */
private static final EquityOptionBlackPresentValueCalculator PVC = EquityOptionBlackPresentValueCalculator.getInstance(); // Vanilla PV Calculator
/** The amount by which to bump the volatility surface */
private static final double SHIFT = 0.0001; // FIXME This really should be configurable by the user!
/**
* Default constructor
*/
public EquityVanillaBarrierOptionVegaMatrixFunction() {
super(ValueRequirementNames.VEGA_QUOTE_MATRIX);
}
@Override
protected Set<ComputedValue> computeValues(final Set<EquityIndexOption> vanillaOptions, final StaticReplicationDataBundle market, final FunctionInputs inputs,
final Set<ValueRequirement> desiredValues, final ComputationTargetSpecification targetSpec, final ValueProperties resultProperties) {
final ValueSpecification resultSpec = new ValueSpecification(getValueRequirementNames()[0], targetSpec, resultProperties);
final NodalDoublesSurface vegaSurface;
if (market.getVolatilitySurface() instanceof BlackVolatilitySurfaceMoneynessFcnBackedByGrid) {
// unpack the market data, including the interpolators
final BlackVolatilitySurfaceMoneynessFcnBackedByGrid surfaceBundle = (BlackVolatilitySurfaceMoneynessFcnBackedByGrid) market.getVolatilitySurface();
final VolatilitySurfaceInterpolator surfaceInterpolator = surfaceBundle.getInterpolator();
final GeneralSmileInterpolator strikeInterpolator = surfaceInterpolator.getSmileInterpolator();
final SmileSurfaceDataBundle volGrid = surfaceBundle.getGridData();
final double[] forwards = volGrid.getForwards();
final double[] expiries = volGrid.getExpiries();
final int nExpiries = volGrid.getNumExpiries();
final double optionExpiry = vanillaOptions.iterator().next().getTimeToExpiry();
final double[][] strikes = volGrid.getStrikes();
final double[][] vols = volGrid.getVolatilities();
// Prices of vanillas in base scenario
final int nVanillas = vanillaOptions.size();
final EquityIndexOption[] vanillas = vanillaOptions.toArray(new EquityIndexOption[nVanillas]);
final Double[] basePrices = new Double[nVanillas];
for (int v = 0; v < nVanillas; v++) {
basePrices[v] = PVC.visitEquityIndexOption(vanillas[v], market);
}
// Smile fits across strikes in base scenario, one per expiry
final Function1D<Double, Double>[] smileFitsBase = surfaceInterpolator.getIndependentSmileFits(volGrid);
// Bump market at each expiry and strike scenario
// In each scenario, reprice each of the underlying vanillaOptions
// NOTE: Only computing down-shift as this appears to produce more stable risk, and is faster
final List<Triple<Double, Double, Double>> triplesExpiryStrikeVega = new ArrayList<>();
final int expiryIndex = SurfaceArrayUtils.getLowerBoundIndex(expiries, optionExpiry);
for (int t = Math.max(0, expiryIndex - 3); t < Math.min(nExpiries, expiryIndex + 4); t++) {
final int nStrikes = strikes[t].length;
int idxLow = SurfaceArrayUtils.getLowerBoundIndex(strikes[t], vanillas[0].getStrike());
int idxHigh = idxLow;
for (int v = 1; v < nVanillas; v++) {
final int idxV = SurfaceArrayUtils.getLowerBoundIndex(strikes[t], vanillas[v].getStrike());
idxLow = Math.min(idxLow, idxV);
idxHigh = Math.max(idxHigh, idxV);
}
for (int k = Math.max(0, idxLow - 6); k < Math.min(nStrikes, idxHigh + 16); k++) {
// Scenario (t,k)
// TODO: REVIEW Each scenario only requires a single new smile fit in k. We only recompute the smile function for the expiry we are bumping..
final double[] bumpedVols = Arrays.copyOf(vols[t], nStrikes);
bumpedVols[k] = vols[t][k] - SHIFT;
final Function1D<Double, Double> thisExpirysSmile = strikeInterpolator.getVolatilityFunction(forwards[t], strikes[t], expiries[t], bumpedVols);
final Function1D<Double, Double>[] scenarioSmileFits = Arrays.copyOf(smileFitsBase, smileFitsBase.length);
scenarioSmileFits[t] = thisExpirysSmile;
final BlackVolatilitySurfaceMoneynessFcnBackedByGrid shiftedSurface = surfaceInterpolator.combineIndependentSmileFits(scenarioSmileFits, volGrid);
final StaticReplicationDataBundle shiftedMarket = market.withShiftedSurface(shiftedSurface);
// Sensitivities
for (int v = 0; v < nVanillas; v++) {
final Double shiftedPV = vanillas[v].accept(PVC, shiftedMarket);
Validate.notNull(shiftedPV, "Null PV in shifted scenario, T = " + expiries[t] + ", k = " + strikes[t][k]);
final Double vega = (shiftedPV - basePrices[v]) / -SHIFT;
final Triple<Double, Double, Double> xyz = new Triple<>(expiries[t], strikes[t][k], vega);
triplesExpiryStrikeVega.add(xyz);
}
}
}
vegaSurface = NodalDoublesSurface.from(triplesExpiryStrikeVega);
// Repackage into DoubleLabelledMatrix2D
// Find unique set of expiries,
final Double[] uniqueX = ArrayUtils.toObject(expiries);
// and strikes
final Set<Double> strikeSet = new HashSet<>();
for (final double[] strike : strikes) {
strikeSet.addAll(Arrays.asList(ArrayUtils.toObject(strike)));
}
final Double[] uniqueY = strikeSet.toArray(new Double[0]);
// Fill matrix with values, zero where no vega is available
final double[][] values = new double[uniqueY.length][uniqueX.length];
int i = 0;
for (final Double x : uniqueX) {
int j = 0;
for (final Double y : uniqueY) {
double vega;
try {
vega = vegaSurface.getZValue(x, y);
} catch (final IllegalArgumentException e) {
vega = 0;
}
values[j++][i] = vega;
}
i++;
}
final DoubleLabelledMatrix2D vegaMatrix = new DoubleLabelledMatrix2D(uniqueX, uniqueY, values);
return Collections.singleton(new ComputedValue(resultSpec, vegaMatrix));
}
throw new OpenGammaRuntimeException("Currently will only accept a VolatilitySurface of type: BlackVolatilitySurfaceMoneynessFcnBackedByGrid");
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) {
final Set<ValueSpecification> results = super.getResults(context, target, inputs);
final SecuritySource securitySource = OpenGammaCompilationContext.getSecuritySource(context);
final FinancialSecurity security = (FinancialSecurity) target.getSecurity();
final ExternalId underlyingId = FinancialSecurityUtils.getUnderlyingId(security);
//final String bbgTicker = getBloombergTicker(securitySource, underlyingId);
final Set<ValueSpecification> resultsWithExtraProperties = Sets.newHashSetWithExpectedSize(results.size());
for (final ValueSpecification spec : results) {
final String name = spec.getValueName();
final ComputationTargetSpecification targetSpec = spec.getTargetSpecification();
final ValueProperties properties = spec.getProperties().copy()
.with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, InstrumentTypeProperties.EQUITY_OPTION)
// .with(ValuePropertyNames.UNDERLYING_TICKER, bbgTicker)
.get();
resultsWithExtraProperties.add(new ValueSpecification(name, targetSpec, properties));
}
return results;
}
}