/**
* 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.HashSet;
import java.util.Map;
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.ExerciseDecisionType;
import com.opengamma.analytics.financial.commodity.definition.SettlementType;
import com.opengamma.analytics.financial.equity.StaticReplicationDataBundle;
import com.opengamma.analytics.financial.equity.option.EquityIndexOption;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.util.time.TimeCalculator;
import com.opengamma.core.security.Security;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
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.ValueSpecification;
import com.opengamma.financial.security.ExerciseTypeAnalyticsVisitorAdapter;
import com.opengamma.financial.security.FinancialSecurityTypes;
import com.opengamma.financial.security.option.BarrierDirection;
import com.opengamma.financial.security.option.BarrierType;
import com.opengamma.financial.security.option.EquityBarrierOptionSecurity;
import com.opengamma.financial.security.option.EuropeanExerciseType;
import com.opengamma.financial.security.option.ExerciseType;
import com.opengamma.financial.security.option.OptionType;
import com.opengamma.id.ExternalId;
import com.opengamma.util.async.AsynchronousExecution;
import com.opengamma.util.money.Currency;
/**
* This function splits a barrier option into a sum of vanilla calls or puts,
* and then calls down to the EquityIndexOptionFunction as its requirements
*/
public abstract class EquityVanillaBarrierOptionBlackFunction extends EquityOptionBlackFunction {
/** The logger */
private static final Logger s_logger = LoggerFactory.getLogger(EquityVanillaBarrierOptionBlackFunction.class);
/**
* @param requirementName The desired output
*/
public EquityVanillaBarrierOptionBlackFunction(final String requirementName) {
super(requirementName);
}
/**
* This method is defined by extending Functions
* @param vanillaOptions Set of EquityIndexOptions that European Barrier is composed of. Binaries are modelled as spreads
* @param market EquityOptionDataBundle
* @param inputs The market data inputs
* @param desiredValues The desired values
* @param targetSpec The target specification of the result
* @param resultProperties The result properties
* @return the result
*/
protected abstract Set<ComputedValue> computeValues(Set<EquityIndexOption> vanillaOptions, StaticReplicationDataBundle market, final FunctionInputs inputs,
final Set<ValueRequirement> desiredValues, final ComputationTargetSpecification targetSpec, final ValueProperties resultProperties);
@Override
protected Set<ComputedValue> computeValues(final InstrumentDerivative derivative, final StaticReplicationDataBundle market, final FunctionInputs inputs,
final Set<ValueRequirement> desiredValues, final ComputationTargetSpecification targetSpec, final ValueProperties resultProperties) {
throw new OpenGammaRuntimeException("Execution wasn't intended to go here. Please review.");
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues)
throws AsynchronousExecution {
final ZonedDateTime now = ZonedDateTime.now(executionContext.getValuationClock());
final EquityBarrierOptionSecurity barrierSec = (EquityBarrierOptionSecurity) target.getSecurity();
final ExternalId underlyingId = barrierSec.getUnderlyingId();
final ValueRequirement desiredValue = desiredValues.iterator().next();
// 1. Get parameters for the smoothing of binary payoffs into put spreads
final String strOH = desiredValue.getConstraint(ValuePropertyNames.BINARY_OVERHEDGE);
if (strOH == null) {
throw new OpenGammaRuntimeException("Could not find: " + ValuePropertyNames.BINARY_OVERHEDGE);
}
final Double overhedge = Double.parseDouble(strOH);
final String strSmooth = desiredValue.getConstraint(ValuePropertyNames.BINARY_SMOOTHING_FULLWIDTH);
if (strSmooth == null) {
throw new OpenGammaRuntimeException("Could not find: " + ValuePropertyNames.BINARY_SMOOTHING_FULLWIDTH);
}
final Double smoothing = Double.parseDouble(strSmooth);
// 2. Break the barrier security into it's vanilla analytic derivatives
final Set<EquityIndexOption> vanillas = vanillaDecomposition(now, barrierSec, smoothing, overhedge);
if (vanillas.iterator().next().getTimeToSettlement() < 0.0) {
throw new OpenGammaRuntimeException("EquityBarrierOptionSecurity with expiry, " + barrierSec.getExpiry().getExpiry().toString() + ", has already settled.");
}
// 3. Build up the market data bundle
final StaticReplicationDataBundle market = buildMarketBundle(underlyingId, executionContext, inputs, target, desiredValues);
// 4. Properties of what's required of this function
final ValueProperties resultProperties = desiredValue.getConstraints().copy().get();
// 5. Compute Values and return
return computeValues(vanillas, market, inputs, desiredValues, target.toSpecification(), resultProperties);
}
@Override
public ComputationTargetType getTargetType() {
return FinancialSecurityTypes.EQUITY_BARRIER_OPTION_SECURITY;
}
@Override
public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) {
final Security security = target.getSecurity();
final ExerciseType exerciseType = ((EquityBarrierOptionSecurity) security).getExerciseType();
if (!(exerciseType instanceof EuropeanExerciseType)) {
return false;
}
return true;
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
// Get requirements common to all EquityIndexOptionss
final Set<ValueRequirement> commonReqs = super.getRequirements(context, target, desiredValue);
if (commonReqs == null) {
return null;
}
// Barriers additionally have parameters for the smoothing of binary payoffs into put spreads
// Return null if they haven't been set so that EquityIndexVanillaBarrierOptionDefaultPropertiesFunction can set them
final Set<String> overhedgeSet = desiredValue.getConstraints().getValues(ValuePropertyNames.BINARY_OVERHEDGE);
if (overhedgeSet == null || overhedgeSet.size() != 1) {
s_logger.info("Could not find {} requirement. Looking for a default..", ValuePropertyNames.BINARY_OVERHEDGE);
return null;
}
final Set<String> smoothingSet = desiredValue.getConstraints().getValues(ValuePropertyNames.BINARY_SMOOTHING_FULLWIDTH);
if (smoothingSet == null || smoothingSet.size() != 1) {
s_logger.info("Could not find {} requirement. Looking for a default..", ValuePropertyNames.BINARY_SMOOTHING_FULLWIDTH);
return null;
}
return commonReqs;
}
@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 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()
.withAny(ValuePropertyNames.BINARY_OVERHEDGE)
.withAny(ValuePropertyNames.BINARY_SMOOTHING_FULLWIDTH)
.get();
resultsWithExtraProperties.add(new ValueSpecification(name, targetSpec, properties));
}
return results;
}
//TODO does all of this work need to be done in financial?
private Set<EquityIndexOption> vanillaDecomposition(final ZonedDateTime valuation, final EquityBarrierOptionSecurity barrierOption,
final double smoothingFullWidth, final double overhedge) {
final Set<EquityIndexOption> vanillas = new HashSet<>();
// Unpack the barrier security
final BarrierDirection bInOut = barrierOption.getBarrierDirection(); // KNOCK_IN, KNOCK_OUT,
final BarrierType bUpDown = barrierOption.getBarrierType(); // UP, DOWN, DOUBLE
final double strike = barrierOption.getStrike();
final double barrier = barrierOption.getBarrierLevel();
final ZonedDateTime expiry = barrierOption.getExpiry().getExpiry();
final double ttm = TimeCalculator.getTimeBetween(valuation, expiry);
final Currency ccy = barrierOption.getCurrency();
final double ptVal = barrierOption.getPointValue();
// parameters to model binary as call/put spread
final double oh = overhedge;
final double width = barrier * smoothingFullWidth; // we specify smoothing as relative value
final double size; // = (barrier - strike ) / smoothingFullWidth;
// There are four cases: UP and IN, UP and OUT, DOWN and IN, DOWN and OUT
// Switch on direction: If UP, use Call Spreads. If DOWN, use Put spreads.
boolean isCall;
double nearStrike;
double farStrike;
switch (bUpDown) {
case UP:
isCall = true;
if (barrierOption.getOptionType().equals(OptionType.PUT)) {
throw new OpenGammaRuntimeException("ONE_LOOK / Vanilla Barriers do not apply to an UP type of Barrier with OptionType.CALL. Confirm that the intended samplingFrequency is ONE_LOOK");
}
if (barrier < strike) {
throw new OpenGammaRuntimeException("Encountered an UP / CALL type of BarrierOption where barrier, " + barrier + ", is below strike, " + strike);
}
size = (barrier - strike) / width;
nearStrike = barrier + oh - 0.5 * width;
farStrike = barrier + oh + 0.5 * width;
break;
case DOWN:
isCall = false;
if (barrierOption.getOptionType().equals(OptionType.CALL)) {
throw new OpenGammaRuntimeException("ONE_LOOK / Vanilla Barriers do not apply to a DOWN type of Barrier with OptionType.PUT. Confirm that the intended samplingFrequency is ONE_LOOK");
}
if (barrier > strike) {
throw new OpenGammaRuntimeException("Encountered a DOWN / PUT type of BarrierOption where barrier, " + barrier + ", is above strike, " + strike);
}
size = (strike - barrier) / width;
nearStrike = barrier + oh + 0.5 * width;
farStrike = barrier + oh - 0.5 * width;
break;
case DOUBLE:
throw new OpenGammaRuntimeException("Encountered an EquityBarrierOption where barrierType is DOUBLE. This isn't yet handled.");
default:
throw new OpenGammaRuntimeException("Encountered an EquityBarrierOption with unexpected BarrierType of: " + bUpDown);
}
// Switch on type
final ExerciseDecisionType exerciseType = barrierOption.getExerciseType().accept(ExerciseTypeAnalyticsVisitorAdapter.getInstance());
switch (bInOut) {
case KNOCK_OUT:
// Long a linear at strike, short a linear at barrier
final EquityIndexOption longlinearK = new EquityIndexOption(ttm, ttm, strike, isCall, ccy, ptVal, exerciseType, SettlementType.PHYSICAL);
final EquityIndexOption shortLinearB = new EquityIndexOption(ttm, ttm, barrier, isCall, ccy, -ptVal, exerciseType, SettlementType.PHYSICAL);
vanillas.add(longlinearK);
vanillas.add(shortLinearB);
// Short a binary of size, barrier - strike. Modelled as call spread struck around strike + oh, with spread of 2*eps
final EquityIndexOption shortNear = new EquityIndexOption(ttm, ttm, nearStrike, isCall, ccy, -1 * ptVal * size, exerciseType, SettlementType.PHYSICAL);
final EquityIndexOption longFar = new EquityIndexOption(ttm, ttm, farStrike, isCall, ccy, ptVal * size, exerciseType, SettlementType.PHYSICAL);
vanillas.add(shortNear);
vanillas.add(longFar);
break;
case KNOCK_IN:
// Long a linear at barrier
final EquityIndexOption longLinearB = new EquityIndexOption(ttm, ttm, barrier, isCall, ccy, ptVal, exerciseType, SettlementType.PHYSICAL);
vanillas.add(longLinearB);
// Long a binary of size, barrier - strike. Modelled as call spread struck around strike + oh, with spread of 2*eps
final EquityIndexOption longNear = new EquityIndexOption(ttm, ttm, nearStrike, isCall, ccy, ptVal * size, exerciseType, SettlementType.PHYSICAL);
final EquityIndexOption shortFar = new EquityIndexOption(ttm, ttm, farStrike, isCall, ccy, -1 * ptVal * size, exerciseType, SettlementType.PHYSICAL);
vanillas.add(longNear);
vanillas.add(shortFar);
break;
default:
throw new OpenGammaRuntimeException("Encountered an EquityBarrierOption with unexpected BarrierDirection of: " + bUpDown);
}
return vanillas;
}
}