/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.model.volatility.surface.black; import static com.opengamma.engine.value.ValuePropertyNames.CURVE; import static com.opengamma.engine.value.ValuePropertyNames.SURFACE; import java.util.Collections; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.ZonedDateTime; import com.google.common.collect.Sets; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.model.interestrate.curve.ForwardCurve; import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository; import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurfaceMoneyness; import com.opengamma.analytics.math.surface.ConstantDoublesSurface; import com.opengamma.analytics.math.surface.Surface; import com.opengamma.analytics.util.time.TimeCalculator; import com.opengamma.core.value.MarketDataRequirementNames; 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.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.curve.forward.ForwardCurveValuePropertyNames; import com.opengamma.financial.security.FinancialSecurityTypes; import com.opengamma.financial.security.option.EquityOptionSecurity; import com.opengamma.id.ExternalId; import com.opengamma.id.UniqueId; import com.opengamma.util.async.AsynchronousExecution; import com.opengamma.util.time.Expiry; import com.opengamma.util.time.ExpiryAccuracy; /** * Computes a flat volatility surface from a single market traded option price, and a forward curve */ public class BlackVolatilitySurfaceSinglePointFunction extends AbstractFunction.NonCompiledInvoker { @Override public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) throws AsynchronousExecution { // The Security itself is the ComputationTarget. From it, we get strike and expiry information to compute implied volatility // The types we're concerned about: EquityOptionSecurity, EquityIndexOptionSecurity, EquityIndexFutureOptionSecurity // For which the strings are: EQUITY_OPTION, EQUITY_INDEX_OPTION, EQUITY_INDEX_FUTURE_OPTION final EquityOptionSecurity security = (EquityOptionSecurity) target.getSecurity(); final double strike = security.getStrike(); final Expiry expiry = security.getExpiry(); if (expiry.getAccuracy().equals(ExpiryAccuracy.MONTH_YEAR) || expiry.getAccuracy().equals(ExpiryAccuracy.YEAR)) { throw new OpenGammaRuntimeException("There is ambiguity in the expiry date of the target security."); } final ZonedDateTime expiryDate = expiry.getExpiry(); final ZonedDateTime valuationDT = ZonedDateTime.now(executionContext.getValuationClock()); double timeToExpiry = TimeCalculator.getTimeBetween(valuationDT, expiryDate); if (timeToExpiry == 0) { // TODO: See JIRA [PLAT-3222] timeToExpiry = 0.0015; } // The ForwardCurve was a requirement. We get both forward and spot values from it. // The latter is used to form the forwardOptionPrice given a (spot) market value final Object forwardCurveObject = inputs.getValue(ValueRequirementNames.FORWARD_CURVE); if (forwardCurveObject == null) { throw new OpenGammaRuntimeException("Could not get forward curve"); } final ForwardCurve forwardCurve = (ForwardCurve) forwardCurveObject; final double forward = forwardCurve.getForward(timeToExpiry); final double spot = forwardCurve.getForward(0.0); // The Volatility Surface is simply a single point, which must be inferred from the market value final Object optionPriceObject = inputs.getComputedValue(MarketDataRequirementNames.MARKET_VALUE); if (optionPriceObject == null) { throw new OpenGammaRuntimeException("Could not get market value of underlying option"); } final double spotOptionPrice = (double) optionPriceObject; final double forwardOptionPrice = spotOptionPrice * forward / spot; double impliedVol = BlackFormulaRepository.impliedVolatility(forwardOptionPrice, forward, strike, timeToExpiry, 0.3); final Surface<Double, Double, Double> surface = ConstantDoublesSurface.from(impliedVol); final BlackVolatilitySurfaceMoneyness impliedVolatilitySurface = new BlackVolatilitySurfaceMoneyness(surface, forwardCurve); final ValueProperties properties = getResultProperties(desiredValues.iterator().next()); final ValueSpecification spec = new ValueSpecification(ValueRequirementNames.BLACK_VOLATILITY_SURFACE, target.toSpecification(), properties); return Collections.singleton(new ComputedValue(spec, impliedVolatilitySurface)); } private ValueProperties getResultProperties(ValueRequirement next) { // TODO Auto-generated method stub return null; } @Override public ComputationTargetType getTargetType() { // TODO: Check this; there is a cast in the execute method, but the comment suggests other securities are also valid return FinancialSecurityTypes.EQUITY_OPTION_SECURITY; } @Override public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) { ValueProperties properties = createValueProperties() .with(SURFACE, "SinglePoint") .with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, getInstrumentType(target)) .withAny(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD) .withAny(CURVE) .get(); return Collections.singleton(new ValueSpecification(ValueRequirementNames.BLACK_VOLATILITY_SURFACE, target.toSpecification(), properties)); } // TODO: Review: I would prefer not to have to create a version of this for each instrument type... // TODO: InstrumentType is hard-coded. Could this be derived from the target Security? private String getInstrumentType(ComputationTarget target) { return InstrumentTypeProperties.EQUITY_OPTION; } @Override public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) { final ValueProperties constraints = desiredValue.getConstraints(); final Set<String> forwardCurveCalculationMethods = constraints.getValues(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD); if (forwardCurveCalculationMethods == null || forwardCurveCalculationMethods.size() != 1) { return null; } final Set<String> surfaceNames = constraints.getValues(SURFACE); // TODO: What is the effect of this? wrt typical setup with actual surfaces? if (surfaceNames == null || surfaceNames.size() != 1) { return null; } final Set<String> curveNames = constraints.getValues(CURVE); if (curveNames == null || curveNames.size() != 1) { return null; } final ValueRequirement forwardCurveRequirement = getForwardCurveRequirement(target, desiredValue); final ValueRequirement optionPriceRequirement = new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, ComputationTargetType.SECURITY, target.getSecurity().getUniqueId()); return Sets.newHashSet(forwardCurveRequirement, optionPriceRequirement); } protected ValueRequirement getForwardCurveRequirement(final ComputationTarget target, final ValueRequirement desiredValue) { final String forwardCurveName = desiredValue.getConstraint(ValuePropertyNames.CURVE); final String curveCalculationMethod = desiredValue.getConstraint(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD); final ValueProperties properties = ValueProperties.builder() .with(ValuePropertyNames.CURVE, forwardCurveName) .with(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD, curveCalculationMethod) .get(); // FIXME This isn't quite going to fly. We need the Security (i.e. the Option) to get its price, ttm and strike, // but we also need the Underlying, either to specify the Forward to build, or we can do it via the Spot price and a discount curve.. // !!! What I've done below will probably work as a gap-solution. Just need to follow EquityForwardCurveFunction's getResults ExternalId underlyingExtId = ((EquityOptionSecurity) target.getSecurity()).getUnderlyingId(); UniqueId underlyingUniqId = UniqueId.of(underlyingExtId); ComputationTargetSpecification underlyingSpec = ComputationTargetSpecification.of(underlyingUniqId); return new ValueRequirement(ValueRequirementNames.FORWARD_CURVE, underlyingSpec, properties); } private static final Logger s_logger = LoggerFactory.getLogger(BlackVolatilitySurfaceSinglePointFunction.class); }