/**
* Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.currency;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeries;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.marketdata.ExternalIdBundleResolver;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueRequirementNames;
import com.opengamma.financial.OpenGammaCompilationContext;
import com.opengamma.financial.analytics.timeseries.DateConstraint;
import com.opengamma.financial.analytics.timeseries.HistoricalTimeSeriesFunctionUtils;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixCross;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixFixed;
import com.opengamma.financial.currency.CurrencyMatrixValue.CurrencyMatrixValueRequirement;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolutionResult;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolver;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.util.money.Currency;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Injects a time series implied from a value from a {@link CurrencyMatrix} into a dependency graph to satisfy the currency requirements generated by {@link CurrencySeriesConversionFunction}.
*/
public class CurrencyMatrixSeriesSourcingFunction extends AbstractCurrencyMatrixSourcingFunction {
private static final Logger s_logger = LoggerFactory.getLogger(CurrencyMatrixSeriesSourcingFunction.class);
// PLAT-2813 Don't need this if we can request HTS requirements directly
private HistoricalTimeSeriesResolver _htsResolver;
public CurrencyMatrixSeriesSourcingFunction() {
this(ValueRequirementNames.HISTORICAL_FX_TIME_SERIES);
}
protected CurrencyMatrixSeriesSourcingFunction(final String valueRequirementName) {
super(valueRequirementName);
}
protected void setHistoricalTimeSeriesResolver(final HistoricalTimeSeriesResolver htsResolver) {
_htsResolver = htsResolver;
}
protected HistoricalTimeSeriesResolver getHistoricalTimeSeriesResolver() {
return _htsResolver;
}
@Override
public void init(final FunctionCompilationContext context) {
super.init(context);
// PLAT-2813 Don't need this if we can request HTS requirements directly
setHistoricalTimeSeriesResolver(OpenGammaCompilationContext.getHistoricalTimeSeriesResolver(context));
}
@Override
protected ValueProperties.Builder createValueProperties() {
final ValueProperties.Builder properties = super.createValueProperties();
properties.withAny(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY)
.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY, HistoricalTimeSeriesFunctionUtils.YES_VALUE, HistoricalTimeSeriesFunctionUtils.NO_VALUE)
.withAny(HistoricalTimeSeriesFunctionUtils.END_DATE_PROPERTY)
.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_END_PROPERTY, HistoricalTimeSeriesFunctionUtils.YES_VALUE, HistoricalTimeSeriesFunctionUtils.NO_VALUE);
return properties;
}
private ValueRequirement getRequirement(final ExternalIdBundleResolver resolver, final CurrencyMatrixValueRequirement valueRequirement, final ValueProperties constraints) {
final ValueRequirement requirement = valueRequirement.getValueRequirement();
// TODO: PLAT-2813 Don't perform the resolution here; request the time series directly
final ExternalIdBundle targetIdentifiers = resolver.getExternalIdBundle(requirement.getTargetReference());
if (targetIdentifiers == null) {
return null;
}
final HistoricalTimeSeriesResolutionResult timeSeries = getHistoricalTimeSeriesResolver().resolve(targetIdentifiers, null, null, null, MarketDataRequirementNames.MARKET_VALUE, null);
if (timeSeries == null) {
return null;
}
// TODO: Requesting the whole time series isn't ideal but we don't know which points will be needed. Could the time series somehow be a lazy-fetch?
// Is this really a problem - caching the whole time series at a calc node may be better than requesting different subsets each time?
return HistoricalTimeSeriesFunctionUtils.createHTSRequirement(timeSeries, MarketDataRequirementNames.MARKET_VALUE, constraints);
}
private boolean getRequirements(final CurrencyMatrix matrix, final ExternalIdBundleResolver resolver, final Set<ValueRequirement> requirements, final Set<Pair<Currency, Currency>> visited,
final Pair<Currency, Currency> currencies, final ValueProperties constraints) {
if (!visited.add(currencies)) {
// Gone round in a loop if we've already seen this pair
throw new IllegalStateException();
}
final CurrencyMatrixValue value = matrix.getConversion(currencies.getFirst(), currencies.getSecond());
if (value != null) {
return value.accept(new CurrencyMatrixValueVisitor<Boolean>() {
@Override
public Boolean visitCross(final CurrencyMatrixCross cross) {
return getRequirements(matrix, resolver, requirements, visited, Pairs.of(currencies.getFirst(), cross.getCrossCurrency()), constraints)
&& getRequirements(matrix, resolver, requirements, visited, Pairs.of(cross.getCrossCurrency(), currencies.getSecond()), constraints);
}
@Override
public Boolean visitFixed(final CurrencyMatrixFixed fixedValue) {
// Literal value - nothing required
return Boolean.TRUE;
}
@Override
public Boolean visitValueRequirement(final CurrencyMatrixValueRequirement valueRequirement) {
final ValueRequirement requirement = getRequirement(resolver, valueRequirement, constraints);
if (requirement == null) {
return Boolean.FALSE;
}
requirements.add(tagInput(requirement, currencies.getFirst(), currencies.getSecond()));
return Boolean.TRUE;
}
});
} else {
return false;
}
}
protected ValueProperties getRequirementConstraints(final ValueRequirement desiredValue) {
final ValueProperties desiredConstraints = desiredValue.getConstraints();
final ValueProperties.Builder requiredConstraints = ValueProperties.builder();
Set<String> values = desiredConstraints.getValues(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY);
if ((values == null) || values.isEmpty()) {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY, DateConstraint.NULL.toString());
} else {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY, values);
}
values = desiredConstraints.getValues(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY);
if ((values == null) || values.isEmpty()) {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY, HistoricalTimeSeriesFunctionUtils.YES_VALUE);
} else {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY, values);
}
values = desiredConstraints.getValues(HistoricalTimeSeriesFunctionUtils.END_DATE_PROPERTY);
if ((values == null) || values.isEmpty()) {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.END_DATE_PROPERTY, DateConstraint.VALUATION_TIME.toString());
} else {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.END_DATE_PROPERTY, values);
}
values = desiredConstraints.getValues(HistoricalTimeSeriesFunctionUtils.INCLUDE_END_PROPERTY);
if ((values == null) || values.isEmpty()) {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_END_PROPERTY, HistoricalTimeSeriesFunctionUtils.YES_VALUE);
} else {
requiredConstraints.with(HistoricalTimeSeriesFunctionUtils.INCLUDE_END_PROPERTY, values);
}
return requiredConstraints.get();
}
@Override
protected boolean getRequirements(FunctionCompilationContext context, ValueRequirement desiredValue, CurrencyMatrix matrix, Set<ValueRequirement> requirements, Currency source, Currency target) {
return getRequirements(matrix, new ExternalIdBundleResolver(context.getComputationTargetResolver()), requirements, new HashSet<Pair<Currency, Currency>>(), Pairs.of(source, target),
getRequirementConstraints(desiredValue));
}
private Object getRate(final CurrencyMatrix matrix, final ExternalIdBundleResolver resolver, final FunctionInputs inputs, final Currency source, final Currency target,
final ValueProperties htsConstraints) {
final CurrencyMatrixValue value = matrix.getConversion(source, target);
final Object rate = value.accept(new CurrencyMatrixValueVisitor<Object>() {
@Override
public Object visitCross(final CurrencyMatrixCross cross) {
final Object r1 = getRate(matrix, resolver, inputs, source, cross.getCrossCurrency(), htsConstraints);
final Object r2 = getRate(matrix, resolver, inputs, cross.getCrossCurrency(), target, htsConstraints);
return createCrossRate(r1, r2);
}
@Override
public Object visitFixed(final CurrencyMatrixFixed fixedValue) {
return fixedValue.getFixedValue();
}
@Override
public Object visitValueRequirement(final CurrencyMatrixValueRequirement valueRequirement) {
final Object marketValue = inputs.getValue(getRequirement(resolver, valueRequirement, htsConstraints));
if (marketValue instanceof DoubleTimeSeries) {
//TODO is this branch ever reached?
DoubleTimeSeries<?> fxRate = (DoubleTimeSeries<?>) marketValue;
if (valueRequirement.isReciprocal()) {
fxRate = fxRate.reciprocal();
}
return fxRate;
} else if (marketValue instanceof HistoricalTimeSeries) {
DoubleTimeSeries<?> fxRate = ((HistoricalTimeSeries) marketValue).getTimeSeries();
if (valueRequirement.isReciprocal()) {
fxRate = fxRate.reciprocal();
}
return fxRate;
} else {
if (marketValue == null) {
// Missing input case; reported elsewhere
return null;
}
throw new IllegalArgumentException("Expected a time series for " + valueRequirement.toString() + ", got " + marketValue.getClass());
}
}
});
s_logger.debug("{} to {} = {}", new Object[] {source, target, rate });
return rate;
}
@Override
protected Object getRate(CurrencyMatrix matrix, ValueRequirement desiredValue, FunctionExecutionContext executionContext, FunctionInputs inputs, Currency source, Currency target) {
return getRate(matrix, new ExternalIdBundleResolver(executionContext.getComputationTargetResolver()), inputs, source, target, getRequirementConstraints(desiredValue));
}
public static ValueRequirement getConversionRequirement(final CurrencyPair currencies) {
return getConversionRequirement(currencies, DateConstraint.NULL, true, DateConstraint.VALUATION_TIME, true);
}
public static ValueRequirement getConversionRequirement(final Currency source, final Currency target) {
return getConversionRequirement(CurrencyPair.of(target, source));
}
public static ValueRequirement getConversionRequirement(final String source, final String target) {
return getConversionRequirement(Currency.of(source), Currency.of(target));
}
public static ValueRequirement getConversionRequirement(final CurrencyPair currencies, final DateConstraint startDate, final boolean includeStart, final DateConstraint endDate,
final boolean includeEnd) {
return new ValueRequirement(ValueRequirementNames.HISTORICAL_FX_TIME_SERIES, CurrencyPair.TYPE.specification(currencies), HistoricalTimeSeriesFunctionUtils.htsConstraints(
ValueProperties.builder(), startDate, includeStart, endDate, includeEnd).get());
}
public static ValueRequirement getConversionRequirement(final Currency source, final Currency target, final DateConstraint startDate, final boolean includeStart, final DateConstraint endDate,
final boolean includeEnd) {
return getConversionRequirement(CurrencyPair.of(target, source));
}
public static ValueRequirement getConversionRequirement(final String source, final String target, final DateConstraint startDate, final boolean includeStart, final DateConstraint endDate,
final boolean includeEnd) {
return getConversionRequirement(Currency.of(source), Currency.of(target));
}
}