/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.volatility.surface;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.Validate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Clock;
import org.threeten.bp.DayOfWeek;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.LocalTime;
import org.threeten.bp.Month;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
import org.threeten.bp.temporal.TemporalAdjuster;
import org.threeten.bp.temporal.TemporalAdjusters;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.core.id.ExternalSchemes;
import com.opengamma.core.marketdatasnapshot.VolatilitySurfaceData;
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.CompiledFunctionDefinition;
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.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.equity.varianceswap.EquityVarianceSwapStaticReplicationFunction;
import com.opengamma.id.ExternalId;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* @deprecated This has been replaced by the pair, RawEquityOptionVolatilitySurfaceDataFunction, EquityFutureOptionVolatilitySurfaceDataFunction
*/
//TODO this class needs to be re-written, as each instrument type needs a different set of inputs
@Deprecated
public class EquityOptionVolatilitySurfaceDataFunctionDeprecated extends AbstractFunction {
private static final Logger s_logger = LoggerFactory.getLogger(EquityOptionVolatilitySurfaceDataFunctionDeprecated.class);
private VolatilitySurfaceDefinition<?, ?> _definition;
private ValueSpecification _result;
private Set<ValueSpecification> _results;
private String _underlyingIdentifierAsString;
private final String _definitionName;
private final String _specificationName;
private final String _instrumentType;
private VolatilitySurfaceSpecification _specification;
public EquityOptionVolatilitySurfaceDataFunctionDeprecated(final String definitionName, final String instrumentType, final String specificationName) {
Validate.notNull(definitionName, "Definition Name");
Validate.notNull(instrumentType, "Instrument Type");
Validate.notNull(specificationName, "Specification Name");
_definition = null;
_definitionName = definitionName;
_instrumentType = instrumentType;
_specificationName = specificationName;
_result = null;
_results = null;
}
public String getCurrencyLabel() {
return _underlyingIdentifierAsString;
}
public String getDefinitionName() {
return _definitionName;
}
public String getSpecificationName() {
return _specificationName;
}
@Override
public void init(final FunctionCompilationContext context) {
_definition = ConfigDBVolatilitySurfaceDefinitionSource.init(context, this).getDefinition(_definitionName, _instrumentType);
if (_definition == null) {
throw new OpenGammaRuntimeException("Couldn't find Equity Option Volatility Surface Definition " + _definitionName);
}
_specification = ConfigDBVolatilitySurfaceSpecificationSource.init(context, this).getSpecification(_specificationName, _instrumentType);
if (_specification == null) {
throw new OpenGammaRuntimeException("Couldn't find Equity Option Volatility Surface Specification " + _specificationName);
}
_result = new ValueSpecification(ValueRequirementNames.STANDARD_VOLATILITY_SURFACE_DATA, ComputationTargetSpecification.of(_definition.getTarget().getUniqueId()),
createValueProperties().with(ValuePropertyNames.SURFACE, _definitionName).with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, _instrumentType)
.withAny(EquityVarianceSwapStaticReplicationFunction.STRIKE_PARAMETERIZATION_METHOD/*, VarianceSwapStaticReplication.StrikeParameterization.STRIKE.toString()*/).get());
_results = Collections.singleton(_result);
}
public static <T extends Comparable<? super T>> List<T> asSortedList(Collection<T> c) {
List<T> list = new ArrayList<T>(c);
java.util.Collections.sort(list);
return list;
}
/**
* // Computes active expiry dates, which fall on the Saturday following the 3rd Friday of an expiry month
*
* @param valDate The evaluation date
* @return The expiry dates
*/
public static TreeSet<LocalDate> getExpirySet(final LocalDate valDate) {
final TemporalAdjuster thirdFriday = TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.FRIDAY);
TreeSet<LocalDate> expirySet = new TreeSet<LocalDate>();
// Add the next six months' Expiries although they are not guaranteed to be traded
final LocalDate thisMonthsExpiry = valDate.with(thirdFriday).plusDays(1);
if (thisMonthsExpiry.isAfter(valDate)) {
expirySet.add(thisMonthsExpiry);
}
for (int m = 1; m <= 6; m++) {
expirySet.add(valDate.plusMonths(m).with(thirdFriday).plusDays(1));
}
// Add the Quarterly IMM months out 3 years
final Set<Month> immQuarters = EnumSet.of(Month.MARCH, Month.JUNE, Month.SEPTEMBER, Month.DECEMBER);
LocalDate nextQuarter = valDate;
do {
nextQuarter = nextQuarter.plusMonths(1);
} while (!immQuarters.contains(nextQuarter.getMonth()));
for (int q = 1; q <= 12; q++) {
expirySet.add(nextQuarter.with(thirdFriday).plusDays(1));
nextQuarter = nextQuarter.plusMonths(3);
}
return expirySet;
}
/**
* Dynamically return an array of strikes given an underlying spot level of the index or price.
*
* @param spot Spot value of the underlying ( e.g. index, stock )
* @param relativeWidth Strike bounds are specified simply: [ spot * ( 1 - width), spot * ( 1 + width) ]
* @param stepSize Difference between each strike in the resulting set. // TODO Extend beyond integer
* @return Long array. The format of this is limiting as these values will be used to create identifiers for the data provider
*/
public static Double[] getStrikes(final Double spot, Double relativeWidth, Integer stepSize) {
Validate.notNull(spot, "Vol Surface Function attempting to build strikes dynamically but spotUnderlying was null");
// I've decided to put in default values // TODO Review
if (relativeWidth == null) {
relativeWidth = 0.6;
}
if (stepSize == null) {
stepSize = 10;
}
// Estimate bounds
double kMin = spot * (1 - relativeWidth);
double kMax = spot * (1 + relativeWidth);
// Round to nearest integer stepSize
kMin = Math.rint(kMin - kMin % stepSize);
kMax = Math.rint(kMax + (stepSize - kMax % stepSize));
// Fill in
int nStrikes = (int) Math.round(1 + (kMax - kMin) / stepSize);
Double[] strikes = new Double[nStrikes];
for (int i = 0; i < nStrikes; i++) {
strikes[i] = kMin + i * stepSize;
}
return strikes;
}
public static <X, Y> Set<ValueRequirement> buildRequirements(final VolatilitySurfaceSpecification specification, final VolatilitySurfaceDefinition<X, Y> definition,
final ZonedDateTime atInstant) {
final Set<ValueRequirement> result = new HashSet<ValueRequirement>();
final BloombergEquityOptionVolatilitySurfaceInstrumentProvider provider = (BloombergEquityOptionVolatilitySurfaceInstrumentProvider) specification.getSurfaceInstrumentProvider();
Object[] expiries = getExpirySet(atInstant.toLocalDate()).toArray();
// !!!!!!!!! SUPPOSE We have some value in the definition that provides us with an estimate of the center strike
Double strikeCenter = 131.3;
Object[] strikes = getStrikes(strikeCenter, 0.6, 5);
for (final Object x : expiries) { // // FIXME Was: definition.getXs()
for (final Object y : strikes) { // FIXME Was: definition.getYs()) {
provider.init(true); // generate puts
final ExternalId putIdentifier = provider.getInstrument((LocalDate) x, (Double) y, atInstant.toLocalDate());
result.add(new ValueRequirement(provider.getDataFieldName(), ComputationTargetType.PRIMITIVE, putIdentifier));
provider.init(false);
final ExternalId callIdentifier = provider.getInstrument((LocalDate) x, (Double) y, atInstant.toLocalDate());
result.add(new ValueRequirement(provider.getDataFieldName(), ComputationTargetType.PRIMITIVE, callIdentifier));
}
}
// add the underlying
final ExternalId temp = ExternalId.of(ExternalSchemes.BLOOMBERG_TICKER, definition.getTarget().getUniqueId().getValue());
result.add(new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, ComputationTargetType.PRIMITIVE, temp));
return result;
}
@Override
public CompiledFunctionDefinition compile(final FunctionCompilationContext context, final Instant atInstant) {
final ZonedDateTime atZDT = ZonedDateTime.ofInstant(atInstant, ZoneOffset.UTC);
final Set<ValueRequirement> requirements = Collections.unmodifiableSet(buildRequirements(_specification, _definition, atZDT));
//TODO ENG-252 see MarketInstrumentImpliedYieldCurveFunction; need to work out the expiry more efficiently
return new AbstractInvokingCompiledFunction(atZDT.with(LocalTime.MIDNIGHT).toInstant(), atZDT.plusDays(1).with(LocalTime.MIDNIGHT).minusNanos(1000000).toInstant()) {
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.ANYTHING; // [PLAT-2286]: something more specific; the definition's target could be any unique identifiable though
}
@SuppressWarnings("synthetic-access")
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext myContext, final ComputationTarget target) {
if (canApplyTo(myContext, target)) {
return _results;
}
return null;
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext myContext, final ComputationTarget target, final ValueRequirement desiredValue) {
if (canApplyTo(myContext, target)) {
return requirements;
}
return null;
}
@SuppressWarnings("synthetic-access")
@Override
public boolean canApplyTo(final FunctionCompilationContext myContext, final ComputationTarget target) {
return ObjectUtils.equals(target.getUniqueId(), _definition.getTarget().getUniqueId());
}
@SuppressWarnings("synthetic-access")
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target,
final Set<ValueRequirement> desiredValues) {
final Clock snapshotClock = executionContext.getValuationClock();
final ExternalId temp = ExternalId.of(ExternalSchemes.BLOOMBERG_TICKER, _definition.getTarget().getUniqueId().getValue());
final ValueRequirement underlyingSpotValueRequirement = new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, ComputationTargetType.PRIMITIVE, temp);
final Double underlyingSpot = (Double) inputs.getValue(underlyingSpotValueRequirement);
if (underlyingSpot == null) {
s_logger.error("Could not get underlying spot value for " + _definition.getTarget().getUniqueId());
return Collections.emptySet();
}
final ZonedDateTime now = ZonedDateTime.now(snapshotClock);
final Map<Pair<Object, Object>, Double> volatilityValues = new HashMap<Pair<Object, Object>, Double>();
Object[] expiries = getExpirySet(atZDT.toLocalDate()).toArray();
for (final Object x : _definition.getXs()) {
for (final Object y : _definition.getYs()) {
final double strike = (Double) y;
final LocalDate expiry = (LocalDate) x;
final BloombergEquityOptionVolatilitySurfaceInstrumentProvider provider = (BloombergEquityOptionVolatilitySurfaceInstrumentProvider) _specification
.getSurfaceInstrumentProvider();
if (strike < underlyingSpot) {
provider.init(false); // generate identifiers for call options
} else {
provider.init(true); // generate identifiers for put options
}
final ExternalId identifier = provider.getInstrument(expiry, strike, now.toLocalDate());
final ValueRequirement requirement = new ValueRequirement(provider.getDataFieldName(), ComputationTargetType.PRIMITIVE, identifier);
if (inputs.getValue(requirement) != null) {
final Double volatility = (Double) inputs.getValue(requirement);
volatilityValues.put(Pairs.of((Object) expiry, (Object) strike), volatility / 100);
}
}
}
final VolatilitySurfaceData<?, ?> volSurfaceData = new VolatilitySurfaceData<Object, Object>(_definition.getName(), _specification.getName(), _definition.getTarget().getUniqueId(),
expiries, _definition.getYs(), volatilityValues);
final ComputedValue resultValue = new ComputedValue(_result, volSurfaceData);
return Collections.singleton(resultValue);
}
@Override
public boolean canHandleMissingInputs() {
return true;
}
};
}
}