/**
* Copyright (C) 2012 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.future;
import java.util.Collections;
import java.util.NoSuchElementException;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.instrument.InstrumentDefinitionWithData;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivative;
import com.opengamma.analytics.financial.interestrate.InstrumentDerivativeVisitor;
import com.opengamma.analytics.financial.simpleinstruments.pricing.SimpleFutureDataBundle;
import com.opengamma.core.holiday.HolidaySource;
import com.opengamma.core.position.Trade;
import com.opengamma.core.region.RegionSource;
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.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.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.OpenGammaCompilationContext;
import com.opengamma.financial.analytics.conversion.FutureTradeConverterDeprecated;
import com.opengamma.financial.analytics.timeseries.DateConstraint;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesBundle;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesFunctionUtils;
import com.opengamma.financial.convention.ConventionBundleSource;
import com.opengamma.financial.security.FinancialSecurityUtils;
import com.opengamma.financial.security.future.FutureSecurity;
import com.opengamma.id.ExternalId;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolutionResult;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolver;
import com.opengamma.util.ArgumentChecker;
/**
* Base class for FuturesSecurity ValueRequirements.
* FuturesFunctions, as the securities are exchange traded, closely resemble MarkToMarketPnLFunction.
* @param <T> The type of the data returned from the calculator
*/
public abstract class FuturesFunction<T> extends AbstractFunction.NonCompiledInvoker {
/** The logger */
private static final Logger s_logger = LoggerFactory.getLogger(FuturesFunction.class);
/** The value requirement name */
private final String _valueRequirementName;
/** The calculator */
private final InstrumentDerivativeVisitor<SimpleFutureDataBundle, T> _calculator;
/** The trade converter */
private FutureTradeConverterDeprecated _tradeConverter;
/** The field name of the historical time series for price, e.g. "PX_LAST", "Close" */
private final String _closingPriceField;
/** The field name of the historical time series for cost of carry e.g. "COST_OF_CARRY" */
private final String _costOfCarryField;
/** key defining how the time series resolution is to occur e.g. "DEFAULT_TSS_CONFIG"*/
private final String _resolutionKey;
/**
* @param valueRequirementName String describes the value requested
* @param calculator The calculator
* @param closingPriceField The field name of the historical time series for price, e.g. "PX_LAST", "Close". Set in *FunctionConfiguration
* @param costOfCarryField The field name of the historical time series for cost of carry e.g. "COST_OF_CARRY". Set in *FunctionConfiguration
* @param resolutionKey The key defining how the time series resolution is to occur e.g. "DEFAULT_TSS_CONFIG"
*/
public FuturesFunction(final String valueRequirementName, final InstrumentDerivativeVisitor<SimpleFutureDataBundle, T> calculator,
final String closingPriceField, final String costOfCarryField, final String resolutionKey) {
ArgumentChecker.notNull(valueRequirementName, "value requirement name");
ArgumentChecker.notNull(calculator, "calculator");
ArgumentChecker.notNull(closingPriceField, "closingPriceField");
ArgumentChecker.notNull(costOfCarryField, "costOfCarryField");
ArgumentChecker.notNull(resolutionKey, "resolutionKey");
_valueRequirementName = valueRequirementName;
_calculator = calculator;
_closingPriceField = closingPriceField;
_costOfCarryField = costOfCarryField;
_resolutionKey = resolutionKey;
}
@Override
public void init(final FunctionCompilationContext context) {
final HolidaySource holidaySource = OpenGammaCompilationContext.getHolidaySource(context);
final RegionSource regionSource = OpenGammaCompilationContext.getRegionSource(context);
final ConventionBundleSource conventionSource = OpenGammaCompilationContext.getConventionBundleSource(context);
final SecuritySource securitySource = OpenGammaCompilationContext.getSecuritySource(context);
_tradeConverter = new FutureTradeConverterDeprecated(securitySource, holidaySource, conventionSource, regionSource);
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
return Collections.singleton(new ValueSpecification(_valueRequirementName, target.toSpecification(), createValueProperties(target).get()));
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) {
final Trade trade = target.getTrade();
final FutureSecurity security = (FutureSecurity) trade.getSecurity();
// Get reference price
final HistoricalTimeSeriesBundle timeSeriesBundle = HistoricalTimeSeriesFunctionUtils.getHistoricalTimeSeriesInputs(executionContext, inputs);
if (timeSeriesBundle == null) {
throw new OpenGammaRuntimeException("Could not get time series bundle for " + trade);
}
Double lastMarginPrice = null;
try {
lastMarginPrice = timeSeriesBundle.get(MarketDataRequirementNames.MARKET_VALUE, security.getExternalIdBundle()).getTimeSeries().getLatestValue();
} catch (final NoSuchElementException e) {
throw new OpenGammaRuntimeException("Time series for " + security.getExternalIdBundle() + " was empty");
}
// Build the analytic's version of the security - the derivative
final ZonedDateTime valuationTime = ZonedDateTime.now(executionContext.getValuationClock());
final InstrumentDefinitionWithData<?, Double> tradeDefinition = _tradeConverter.convert(trade);
double referencePrice = lastMarginPrice; // TODO: Decide if this logic should be here or in toDerivative.
if (trade.getTradeDate() != null) {
if (trade.getTradeDate().isEqual(valuationTime.toLocalDate())) { // Transaction is on pricing date.if (trade.getPremium() != null) {
if (trade.getPremium() != null) {
referencePrice = trade.getPremium(); // TODO: The trade price is stored in the trade premium. This has to be corrected.
}
}
}
final InstrumentDerivative derivative = tradeDefinition.toDerivative(valuationTime, referencePrice);
// Build the DataBundle it requires
final ValueRequirement desiredValue = desiredValues.iterator().next();
final SimpleFutureDataBundle dataBundle = getFutureDataBundle(security, inputs, timeSeriesBundle, desiredValue);
// Call OG-Analytics
final T value = derivative.accept(_calculator, dataBundle);
final T scaledValue = applyTradeScaling(trade, value);
final ValueSpecification specification = new ValueSpecification(_valueRequirementName, target.toSpecification(), createValueProperties(target, desiredValue).get());
return Collections.singleton(new ComputedValue(specification, scaledValue));
}
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.TRADE;
}
@Override
public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) {
final Security security = target.getTrade().getSecurity();
return security instanceof FutureSecurity;
}
/**
* Creates general value properties
* @param target The target
* @return The value properties
*/
protected abstract ValueProperties.Builder createValueProperties(final ComputationTarget target);
/**
* Creates value properties with the property values set
* @param target The target
* @param desiredValue The desired value
* @return The value properties
*/
protected ValueProperties.Builder createValueProperties(final ComputationTarget target, final ValueRequirement desiredValue) {
return createValueProperties(target);
}
/**
* Creates the data bundle used in futures pricing
* @param security The security
* @param inputs The market data inputs
* @param timeSeriesBundle A bundle containing time series
* @param desiredValue The desired value
* @return The data bundle used for pricing futures
*/
protected abstract SimpleFutureDataBundle getFutureDataBundle(final FutureSecurity security, final FunctionInputs inputs,
final HistoricalTimeSeriesBundle timeSeriesBundle, final ValueRequirement desiredValue);
/**
* @return The calculator
*/
protected InstrumentDerivativeVisitor<SimpleFutureDataBundle, T> getCalculator() {
return _calculator;
}
/**
* Gets the spot value requirement
* @param security The security
* @return The spot asset value requirement if the future has a spot asset id, null otherwise
*/
protected ValueRequirement getSpotAssetRequirement(final FutureSecurity security) {
try {
final ExternalId spotAssetId = getSpotAssetId(security);
if (spotAssetId == null) {
return null;
}
final ValueRequirement req = new ValueRequirement(MarketDataRequirementNames.MARKET_VALUE, ComputationTargetType.PRIMITIVE, spotAssetId);
return req;
} catch (final UnsupportedOperationException e) {
s_logger.info(e.getMessage());
return null;
}
}
/**
* Gets the spot asset id from a security
* @param sec The security
* @return The spot asset id, null if not available
*/
protected ExternalId getSpotAssetId(final FutureSecurity sec) {
final ExternalId spotAssetId = FinancialSecurityUtils.getUnderlyingId(sec);
if (spotAssetId == null) {
s_logger.info("Failed to find spot asset id (category = {}) for future with id bundle {}", sec.getContractCategory(), sec.getExternalIdBundle());
return null;
}
return spotAssetId;
}
/**
* Gets the spot value
* @param inputs The market data inputs
* @return The spot value
* @throws OpenGammaRuntimeException If the spot value is null
*/
protected Double getSpot(final FunctionInputs inputs) {
final Object spotObject = inputs.getValue(MarketDataRequirementNames.MARKET_VALUE);
if (spotObject == null) {
throw new OpenGammaRuntimeException("Could not get the spot value");
}
return (Double) spotObject;
}
/**
* Gets the historical time series of the future price
* @param context The compilation context
* @param security The security
* @return The value requirement for the time series of future price
*/
protected ValueRequirement getReferencePriceRequirement(final FunctionCompilationContext context, final FutureSecurity security) {
final HistoricalTimeSeriesResolver resolver = OpenGammaCompilationContext.getHistoricalTimeSeriesResolver(context);
final ExternalIdBundle idBundle = security.getExternalIdBundle();
// TODO CASE: Test that you can change field to MARKET_VALUE because of the FieldAdjustment in ActivHistoricalTimeSeriesSourceComponentFactory.createResolver
final HistoricalTimeSeriesResolutionResult timeSeries = resolver.resolve(security.getExternalIdBundle(), null, null, null, MarketDataRequirementNames.MARKET_VALUE, getResolutionKey());
if (timeSeries == null) {
s_logger.warn("Failed to find time series for: " + idBundle.toString());
return null;
}
return HistoricalTimeSeriesFunctionUtils.createHTSRequirement(timeSeries, MarketDataRequirementNames.MARKET_VALUE,
DateConstraint.VALUATION_TIME.minus(Period.ofDays(7)), true, DateConstraint.VALUATION_TIME, true);
}
/**
* FuturesFunction acts upon ComputationTargetType.TRADE, but many of the calculators used in the execute() method really operate
* as if they acted upon ComputationTargetType.SECUIRITY.
* For this reason, it is the responsibility of the Function here to scale by trade.getQuantity() if appropriate.
* @param trade Computation Target
* @param value Computed Result to be scaled
* @return Scaled result
*/
protected T applyTradeScaling(final Trade trade, T value) {
return value;
}
protected String getClosingPriceField() {
return _closingPriceField;
}
protected String getCostOfCarryField() {
return _costOfCarryField;
}
protected String getResolutionKey() {
return _resolutionKey;
}
}