/**
* 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 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.interestrate.curve.YieldCurve;
import com.opengamma.analytics.financial.model.volatility.BlackFormulaRepository;
import com.opengamma.analytics.financial.model.volatility.surface.BlackVolatilitySurface;
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.security.Security;
import com.opengamma.core.security.SecuritySource;
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.curve.forward.ForwardCurveValuePropertyNames;
import com.opengamma.financial.analytics.model.equity.option.EquityOptionFunction;
import com.opengamma.financial.security.FinancialSecurity;
import com.opengamma.financial.security.FinancialSecurityUtils;
import com.opengamma.financial.security.future.IndexFutureSecurity;
import com.opengamma.financial.security.option.EquityIndexFutureOptionSecurity;
import com.opengamma.financial.security.option.EquityIndexOptionSecurity;
import com.opengamma.financial.security.option.EquityOptionSecurity;
import com.opengamma.financial.security.option.OptionType;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.util.async.AsynchronousExecution;
import com.opengamma.util.money.Currency;
import com.opengamma.util.time.Expiry;
import com.opengamma.util.time.ExpiryAccuracy;
/**
* Produces a {@link ValueRequirementNames#BLACK_VOLATILITY_SURFACE}
* from Forward and Discounting Curves, and the Option's {@link MarketDataRequirementNames#MARKET_VALUE} or {@link ValueRequirementNames#MARK_PREVIOUS}.<p>
*/
public class EquityBlackVolatilitySurfaceFromSinglePriceFunction extends AbstractFunction.NonCompiledInvoker {
private static final Logger s_logger = LoggerFactory.getLogger(EquityOptionFunction.class);
/**
* Property name for what to do if Implied Vol is undefined <p>
* <p>
* Property used to select method of dealing with rare case in which option and forward prices are such
* that the implied volatility is not defined.<p>
* This occurs when the discounted payoff is worth more than the option price.<p>
* See child classes of this one.
*/
public static final String PROPERTY_IMPLIED_VOL_BACKUP = "ImpliedVolBackup";
/**
* Selection of {@link PROPERTY_IMPLIED_VOL_BACKUP} which will throw an error
* if implied vol is undefined
*/
public static final String NO_VOL_BACKUP = "None";
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.SECURITY;
}
@Override
public Set<ValueSpecification> getResults(FunctionCompilationContext context, ComputationTarget target) {
final ValueProperties properties = getResultProperties();
return Collections.singleton(new ValueSpecification(ValueRequirementNames.BLACK_VOLATILITY_SURFACE, target.toSpecification(), properties));
}
public ValueProperties getResultProperties() {
ValueProperties properties = createValueProperties()
.withAny(ValuePropertyNames.DISCOUNTING_CURVE_NAME)
.withAny(ValuePropertyNames.CURVE_CALCULATION_CONFIG)
.withAny(ValuePropertyNames.FORWARD_CURVE_NAME)
.withAny(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD)
.get();
return properties;
}
private ValueProperties getResultProperties(Set<ValueRequirement> desiredValues) {
return desiredValues.iterator().next().getConstraints();
}
protected Set<ValueRequirement> getAddlRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) {
return Collections.emptySet();
}
@Override
public Set<ValueRequirement> getRequirements(FunctionCompilationContext context, ComputationTarget target, ValueRequirement desiredValue) {
final ValueProperties constraints = desiredValue.getConstraints();
Set<ValueRequirement> requirements = Sets.newHashSet();
// 1. Market/Closing Value Requirement
requirements.add(new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, target.toSpecification()));
// 2. Discounting Curve Requirements
final FinancialSecurity security = (FinancialSecurity) target.getSecurity();
final Currency ccy = FinancialSecurityUtils.getCurrency(security);
final String discountingCurveName = constraints.getStrictValue(ValuePropertyNames.DISCOUNTING_CURVE_NAME);
if (discountingCurveName == null) {
return null;
}
final String curveCalculationConfig = constraints.getStrictValue(ValuePropertyNames.CURVE_CALCULATION_CONFIG);
if (curveCalculationConfig == null) {
return null;
}
final ValueProperties fundingCurveProperties = ValueProperties.builder()
.with(ValuePropertyNames.CURVE, discountingCurveName)
.with(ValuePropertyNames.CURVE_CALCULATION_CONFIG, curveCalculationConfig)
.get();
final ValueRequirement discountCurveRequirement = new ValueRequirement(ValueRequirementNames.YIELD_CURVE, ComputationTargetSpecification.of(ccy), fundingCurveProperties);
requirements.add(discountCurveRequirement);
// 3. Forward Curve Requirement
final String forwardCurveName = constraints.getStrictValue(ValuePropertyNames.FORWARD_CURVE_NAME);
if (forwardCurveName == null) {
return null;
}
final String forwardCurveCalculationMethod = constraints.getStrictValue(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD);
if (forwardCurveCalculationMethod == null) {
return null;
}
final ValueProperties forwardCurveProperties = ValueProperties.builder()
.with(ValuePropertyNames.CURVE, forwardCurveName)
.with(ForwardCurveValuePropertyNames.PROPERTY_FORWARD_CURVE_CALCULATION_METHOD, forwardCurveCalculationMethod)
.get();
// Next we need to determine the correct target for the ForwardCurve
final ExternalId underlyingId = FinancialSecurityUtils.getUnderlyingId(security);
if (underlyingId == null) {
s_logger.debug("Did not find ExternalId for Security: {}", security);
return null;
}
if (security instanceof EquityIndexFutureOptionSecurity) {
final SecuritySource securitySource = context.getSecuritySource();
IndexFutureSecurity future = (IndexFutureSecurity) securitySource.getSingle(ExternalIdBundle.of(underlyingId), context.getComputationTargetResolver().getVersionCorrection());
if (future == null) {
s_logger.debug("Did not find anything in SecuritySource for ExternalId: {}", underlyingId);
return null;
}
final ExternalId indexId = future.getUnderlyingId();
if (indexId == null) {
s_logger.debug("Did not find ExternalId for underlying future security: {}", future);
return null;
}
requirements.add(new ValueRequirement(ValueRequirementNames.FORWARD_CURVE, ComputationTargetType.PRIMITIVE, indexId, forwardCurveProperties));
} else {
requirements.add(new ValueRequirement(ValueRequirementNames.FORWARD_CURVE, ComputationTargetType.PRIMITIVE, underlyingId, forwardCurveProperties));
}
// 4. Add any additional requirements, and return
Set<ValueRequirement> addlRequirements = getAddlRequirements(context, target, desiredValue);
if (addlRequirements != null) {
requirements.addAll(addlRequirements);
}
return requirements;
}
@Override
public Set<ComputedValue> execute(FunctionExecutionContext executionContext, FunctionInputs inputs, ComputationTarget target, Set<ValueRequirement> desiredValues) throws AsynchronousExecution {
final BlackVolatilitySurface<?> blackVolSurf = getVolatilitySurface(executionContext, inputs, target, desiredValues);
final ValueSpecification spec = new ValueSpecification(ValueRequirementNames.BLACK_VOLATILITY_SURFACE, target.toSpecification(), getResultProperties(desiredValues));
return Collections.singleton(new ComputedValue(spec, blackVolSurf));
}
// The Volatility Surface is simply a single point, which must be inferred from the market value
protected BlackVolatilitySurface<?> getVolatilitySurface(final FunctionExecutionContext executionContext,
final FunctionInputs inputs, final ComputationTarget target, Set<ValueRequirement> desiredValues) {
// First, get the market value
final ComputedValue optionPriceValue = inputs.getComputedValue(new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, target.toSpecification()));
if (optionPriceValue == null) {
s_logger.error("Could not get value of security: {}", target.getSecurity());
return null;
}
final Double spotOptionPrice = (Double) optionPriceValue.getValue();
// From the Security, we get strike and expiry information to compute implied volatility
final double strike;
final Expiry expiry;
final boolean isCall;
final Security security = target.getSecurity();
if (security instanceof EquityOptionSecurity) {
final EquityOptionSecurity option = (EquityOptionSecurity) security;
strike = option.getStrike();
expiry = option.getExpiry();
isCall = option.getOptionType().equals(OptionType.CALL);
} else if (security instanceof EquityIndexOptionSecurity) {
final EquityIndexOptionSecurity option = (EquityIndexOptionSecurity) security;
strike = option.getStrike();
expiry = option.getExpiry();
isCall = option.getOptionType().equals(OptionType.CALL);
} else if (security instanceof EquityIndexFutureOptionSecurity) {
final EquityIndexFutureOptionSecurity option = (EquityIndexFutureOptionSecurity) security;
strike = option.getStrike();
expiry = option.getExpiry();
isCall = option.getOptionType().equals(OptionType.CALL);
} else {
throw new OpenGammaRuntimeException("Security type not handled," + security.getName());
}
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;
}
// From the curve requirements, we get the forward and zero coupon prices
final ForwardCurve forwardCurve = getForwardCurve(inputs);
final double forward = forwardCurve.getForward(timeToExpiry);
final double discountFactor = getDiscountingCurve(inputs).getDiscountFactor(timeToExpiry);
// From the option value, we invert the Black formula
double forwardOptionPrice = spotOptionPrice / discountFactor;
final Double impliedVol;
final double intrinsic = Math.max(0.0, (forward - strike) * (isCall ? 1.0 : -1.0));
if (intrinsic >= forwardOptionPrice) {
s_logger.info("Implied Vol Error: " + security.getName() + " - Intrinsic value (" + intrinsic + ") > price (" + forwardOptionPrice + ")!");
impliedVol = getImpliedVolIfPriceBelowPayoff(inputs, target, discountFactor, forward, strike, timeToExpiry, isCall);
} else {
impliedVol = BlackFormulaRepository.impliedVolatility(forwardOptionPrice, forward, strike, timeToExpiry, isCall);
}
if (impliedVol == null) {
s_logger.error("Unable to compute implied vol");
return null;
}
final Surface<Double, Double, Double> surface = ConstantDoublesSurface.from(impliedVol);
final BlackVolatilitySurfaceMoneyness impliedVolatilitySurface = new BlackVolatilitySurfaceMoneyness(surface, forwardCurve);
return impliedVolatilitySurface;
}
protected Double getImpliedVolIfPriceBelowPayoff(final FunctionInputs inputs, final ComputationTarget target,
final double discountFactor, final double forward, final double strike, final double timeToExpiry, final boolean isCall) {
s_logger.error("Setting implied volatility to null");
return null;
}
protected YieldCurve getDiscountingCurve(final FunctionInputs inputs) {
final Object discountingObject = inputs.getValue(ValueRequirementNames.YIELD_CURVE);
if (discountingObject == null) {
throw new OpenGammaRuntimeException("Could not get discounting Curve");
}
if (!(discountingObject instanceof YieldCurve)) {
throw new IllegalArgumentException("Can only handle YieldCurve");
}
return (YieldCurve) discountingObject;
}
protected ForwardCurve getForwardCurve(final FunctionInputs inputs) {
final Object forwardCurveObject = inputs.getValue(ValueRequirementNames.FORWARD_CURVE);
if (forwardCurveObject == null) {
throw new OpenGammaRuntimeException("Could not get forward curve");
}
return (ForwardCurve) forwardCurveObject;
}
}