/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.curve.future;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Clock;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.LocalTime;
import org.threeten.bp.ZoneOffset;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.math.curve.NodalDoublesCurve;
import com.opengamma.analytics.util.time.TimeCalculator;
import com.opengamma.engine.ComputationTarget;
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.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.equity.EquitySecurityUtils;
import com.opengamma.financial.analytics.volatility.surface.FuturePriceCurveDefinition;
import com.opengamma.financial.analytics.volatility.surface.FuturePriceCurveInstrumentProvider;
import com.opengamma.financial.analytics.volatility.surface.FuturePriceCurveSpecification;
import com.opengamma.financial.convention.calendar.Calendar;
import com.opengamma.financial.convention.calendar.MondayToFridayCalendar;
import com.opengamma.financial.convention.expirycalc.ExchangeTradedInstrumentExpiryCalculator;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdentifiable;
import com.opengamma.id.VersionCorrection;
/**
* Function providing an equity future curve.
*/
public class EquityFuturePriceCurveFunction extends FuturePriceCurveFunction {
private static final Logger s_logger = LoggerFactory.getLogger(FuturePriceCurveFunction.class);
private static final Calendar WEEKDAYS = new MondayToFridayCalendar("MTWThF");
@Override
protected String getInstrumentType() {
return InstrumentTypeProperties.EQUITY_FUTURE_PRICE;
}
@SuppressWarnings("unchecked")
private FuturePriceCurveDefinition<Object> getCurveDefinition(final ComputationTarget target, final String definitionName, final VersionCorrection versionCorrection) {
if (!(target.getValue() instanceof ExternalIdentifiable)) {
return null;
}
ExternalId id = ((ExternalIdentifiable) target.getValue()).getExternalId();
final String ticker = EquitySecurityUtils.getIndexOrEquityName(id);
final String fullDefinitionName = definitionName + "_" + ticker;
return (FuturePriceCurveDefinition<Object>) getFuturePriceCurveDefinitionSource().getDefinition(fullDefinitionName, getInstrumentType(), versionCorrection);
}
private FuturePriceCurveSpecification getCurveSpecification(final ComputationTarget target, final String specificationName, final VersionCorrection versionCorrection) {
if (!(target.getValue() instanceof ExternalIdentifiable)) {
return null;
}
ExternalId id = ((ExternalIdentifiable) target.getValue()).getExternalId();
final String ticker = EquitySecurityUtils.getIndexOrEquityName(id);
final String fullSpecificationName = specificationName + "_" + ticker;
return getFuturePriceCurveSpecificationSource().getSpecification(fullSpecificationName, getInstrumentType(), versionCorrection);
}
@SuppressWarnings("unchecked")
public static Set<ValueRequirement> buildRequirements(final FuturePriceCurveSpecification futurePriceCurveSpecification,
final FuturePriceCurveDefinition<Object> futurePriceCurveDefinition, final ValueRequirement desiredValue, final ZonedDateTime atInstant) {
final Set<ValueRequirement> result = new HashSet<>();
final FuturePriceCurveInstrumentProvider<Number> futurePriceCurveProvider = (FuturePriceCurveInstrumentProvider<Number>) futurePriceCurveSpecification.getCurveInstrumentProvider();
final String dataFieldName = getDataFieldName(futurePriceCurveProvider, desiredValue);
for (final Object x : futurePriceCurveDefinition.getXs()) {
final ExternalId identifier = futurePriceCurveProvider.getInstrument((Number) x, atInstant.toLocalDate());
result.add(new ValueRequirement(dataFieldName, ComputationTargetType.PRIMITIVE, identifier));
}
return result;
}
private static String getDataFieldName(FuturePriceCurveInstrumentProvider<Number> futurePriceCurveProvider, ValueRequirement desiredValue) {
return futurePriceCurveProvider.getDataFieldName();
}
@Override
protected Double getTimeToMaturity(final int n, final LocalDate date, final Calendar calendar) {
throw new OpenGammaRuntimeException("Unexpected call");
}
/* Spot value of the equity index or name */
private ValueRequirement getSpotRequirement(final ComputationTarget target, final String dataFieldName) {
return new ValueRequirement(dataFieldName, ComputationTargetType.PRIMITIVE, target.getUniqueId());
}
@Override
public CompiledFunctionDefinition compile(final FunctionCompilationContext context, final Instant atInstant) {
final ZonedDateTime atZDT = ZonedDateTime.ofInstant(atInstant, ZoneOffset.UTC);
return new AbstractInvokingCompiledFunction(atZDT.with(LocalTime.MIDNIGHT), atZDT.plusDays(1).with(LocalTime.MIDNIGHT).minusNanos(1000000)) {
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.PRIMITIVE;
}
@SuppressWarnings("synthetic-access")
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext myContext, final ComputationTarget target) {
final ValueProperties curveProperties = createValueProperties().withAny(ValuePropertyNames.CURVE)
.with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, getInstrumentType()).get();
final ValueSpecification futurePriceCurveResult = new ValueSpecification(ValueRequirementNames.FUTURE_PRICE_CURVE_DATA, target.toSpecification(), curveProperties);
return Collections.singleton(futurePriceCurveResult);
}
@SuppressWarnings({"synthetic-access", "unchecked" })
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext myContext, final ComputationTarget target, final ValueRequirement desiredValue) {
final ValueProperties constraints = desiredValue.getConstraints();
final String curveName = constraints.getStrictValue(ValuePropertyNames.CURVE);
if (curveName == null) {
return null;
}
final String curveDefinitionName = curveName;
final String curveSpecificationName = curveName;
final VersionCorrection versionCorrection = myContext.getComputationTargetResolver().getVersionCorrection();
final FuturePriceCurveDefinition<Object> priceCurveDefinition = getCurveDefinition(target, curveDefinitionName, versionCorrection);
if (priceCurveDefinition == null) {
s_logger.error("Price curve definition for target {} with curve name {} and instrument type {} was null", new Object[] {target, curveDefinitionName, getInstrumentType() });
return null;
}
final FuturePriceCurveSpecification priceCurveSpecification = getCurveSpecification(target, curveSpecificationName, versionCorrection);
if (priceCurveSpecification == null) {
s_logger.error("Price curve specification for target {} with curve name {} and instrument type {} was null", new Object[] {target, curveSpecificationName, getInstrumentType() });
return null;
}
final FuturePriceCurveInstrumentProvider<Number> futurePriceCurveProvider = (FuturePriceCurveInstrumentProvider<Number>) priceCurveSpecification.getCurveInstrumentProvider();
final String dataFieldName = getDataFieldName(futurePriceCurveProvider, desiredValue);
Set<ValueRequirement> requirements = Sets.newHashSet();
requirements.add(getSpotRequirement(target, dataFieldName));
requirements.addAll(buildRequirements(priceCurveSpecification, priceCurveDefinition, desiredValue, atZDT));
return requirements;
}
@Override
public boolean canHandleMissingInputs() {
return true;
}
@Override
public boolean canHandleMissingRequirements() {
return true;
}
@SuppressWarnings({"synthetic-access", "unchecked" })
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target,
final Set<ValueRequirement> desiredValues) {
final ValueRequirement desiredValue = desiredValues.iterator().next();
final String curveName = desiredValue.getConstraint(ValuePropertyNames.CURVE);
final String curveDefinitionName = curveName;
final String curveSpecificationName = curveName;
final VersionCorrection versionCorrection = executionContext.getComputationTargetResolver().getVersionCorrection();
final FuturePriceCurveDefinition<Object> priceCurveDefinition = getCurveDefinition(target, curveDefinitionName, versionCorrection);
final FuturePriceCurveSpecification priceCurveSpecification = getCurveSpecification(target, curveSpecificationName, versionCorrection);
final Clock snapshotClock = executionContext.getValuationClock();
final ZonedDateTime now = ZonedDateTime.now(snapshotClock);
final DoubleArrayList xList = new DoubleArrayList();
final DoubleArrayList prices = new DoubleArrayList();
final FuturePriceCurveInstrumentProvider<Number> futurePriceCurveProvider = (FuturePriceCurveInstrumentProvider<Number>) priceCurveSpecification.getCurveInstrumentProvider();
final String dataFieldName = getDataFieldName(futurePriceCurveProvider, desiredValue);
final ExchangeTradedInstrumentExpiryCalculator expiryCalc = futurePriceCurveProvider.getExpiryRuleCalculator();
final LocalDate valDate = now.toLocalDate();
if (inputs.getAllValues().isEmpty()) {
throw new OpenGammaRuntimeException("Could not get any data for future price curve called " + curveSpecificationName);
}
// Add spot
final Object spotUnderlying = inputs.getValue(getSpotRequirement(target, dataFieldName));
if (spotUnderlying != null) {
xList.add(0.0);
prices.add((Double) spotUnderlying);
}
// Add futures
for (final Object x : priceCurveDefinition.getXs()) {
final Number xNum = (Number) x;
final ExternalId identifier = futurePriceCurveProvider.getInstrument(xNum, valDate);
final ValueRequirement requirement = new ValueRequirement(dataFieldName, ComputationTargetType.PRIMITIVE, identifier);
Double futurePrice = null;
if (inputs.getValue(requirement) != null) {
futurePrice = (Double) inputs.getValue(requirement);
if (futurePrice != null) {
LocalDate expiry = expiryCalc.getExpiryDate(xNum.intValue(), valDate, WEEKDAYS); // TODO Add true holiday calendar
final Double ttm = TimeCalculator.getTimeBetween(valDate, expiry);
xList.add(ttm);
prices.add(futurePrice);
}
}
}
final ValueSpecification futurePriceCurveResult = new ValueSpecification(ValueRequirementNames.FUTURE_PRICE_CURVE_DATA, target.toSpecification(), createValueProperties()
.with(ValuePropertyNames.CURVE, curveName).with(InstrumentTypeProperties.PROPERTY_SURFACE_INSTRUMENT_TYPE, getInstrumentType()).get());
final NodalDoublesCurve curve = NodalDoublesCurve.from(xList.toDoubleArray(), prices.toDoubleArray());
final ComputedValue futurePriceCurveResultValue = new ComputedValue(futurePriceCurveResult, curve);
return Sets.newHashSet(futurePriceCurveResultValue);
}
};
}
}