/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.currency;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.ComputationTargetSpecification;
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.ValueProperties.Builder;
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.DoubleLabelledMatrix1D;
import com.opengamma.util.ArgumentChecker;
/**
* Converts a value from one currency to another, preserving all other properties.
*/
public class CurrencyConversionFunction extends AbstractFunction.NonCompiledInvoker {
private static final String DEFAULT_CURRENCY_INJECTION = ValuePropertyNames.OUTPUT_RESERVED_PREFIX + ValuePropertyNames.CURRENCY;
/**
* The property this function will put on an output indicating the currency of the original value.
*/
public static final String ORIGINAL_CURRENCY = "Original" + ValuePropertyNames.CURRENCY;
private static final String CONVERSION_METHOD_VALUE = "Single";
private static final Logger s_logger = LoggerFactory.getLogger(CurrencyConversionFunction.class);
private static final ComputationTargetType TYPE = ComputationTargetType.PORTFOLIO_NODE.or(ComputationTargetType.POSITION).or(ComputationTargetType.SECURITY).or(ComputationTargetType.TRADE);
private final Set<String> _valueNames;
private boolean _allowViewDefaultCurrency; // = false;
public CurrencyConversionFunction(final String valueName) {
ArgumentChecker.notNull(valueName, "valueName");
_valueNames = Collections.singleton(valueName);
}
public CurrencyConversionFunction(final String... valueNames) {
ArgumentChecker.notEmpty(valueNames, "valueNames");
_valueNames = new HashSet<String>(Arrays.asList(valueNames));
}
protected Set<String> getValueNames() {
return _valueNames;
}
public void setAllowViewDefaultCurrency(final boolean allowViewDefaultCurrency) {
_allowViewDefaultCurrency = allowViewDefaultCurrency;
}
public boolean isAllowViewDefaultCurrency() {
return _allowViewDefaultCurrency;
}
private ValueRequirement getInputValueRequirement(final ComputationTargetSpecification targetSpec, final ValueRequirement desiredValue) {
Builder properties = desiredValue.getConstraints().copy()
.withoutAny(DEFAULT_CURRENCY_INJECTION)
.withoutAny(ValuePropertyNames.CONVERSION_METHOD)
.withAny(ValuePropertyNames.CURRENCY).withoutAny(ORIGINAL_CURRENCY);
return new ValueRequirement(desiredValue.getValueName(), targetSpec, properties.get());
}
private ValueRequirement getInputValueRequirement(final ComputationTargetSpecification targetSpec, final ValueRequirement desiredValue, final String forceCurrency) {
return new ValueRequirement(desiredValue.getValueName(), targetSpec, desiredValue.getConstraints().copy().withoutAny(ValuePropertyNames.CURRENCY).with(
ValuePropertyNames.CURRENCY, forceCurrency).withoutAny(ORIGINAL_CURRENCY).withOptional(DEFAULT_CURRENCY_INJECTION).get());
}
/**
* Divides the value by the conversion rate. Override this in a subclass for anything more elaborate - e.g. if the value is in "somethings per currency unit foo" so needs multiplying by the rate
* instead.
*
* @param value input value to convert
* @param conversionRate conversion rate to use
* @return the converted value
*/
protected double convertDouble(final double value, final double conversionRate) {
return value / conversionRate;
}
protected double[] convertDoubleArray(final double[] values, final double conversionRate) {
final double[] newValues = new double[values.length];
for (int i = 0; i < values.length; i++) {
newValues[i] = convertDouble(values[i], conversionRate);
}
return newValues;
}
protected DoubleLabelledMatrix1D convertDoubleLabelledMatrix1D(final DoubleLabelledMatrix1D value, final double conversionRate) {
return new DoubleLabelledMatrix1D(value.getKeys(), value.getLabels(), convertDoubleArray(value.getValues(), conversionRate));
}
/**
* Delegates off to the other convert methods depending on the type of value.
*
* @param inputValue input value to convert
* @param desiredValue requested value requirement
* @param conversionRate conversion rate to use
* @return the converted value
*/
protected Object convertValue(final ComputedValue inputValue, final ValueRequirement desiredValue, final double conversionRate) {
final Object value = inputValue.getValue();
if (value instanceof Double) {
return convertDouble((Double) value, conversionRate);
} else if (value instanceof DoubleLabelledMatrix1D) {
return convertDoubleLabelledMatrix1D((DoubleLabelledMatrix1D) value, conversionRate);
} else {
s_logger.error("Can't convert object with type {} to {}", inputValue.getValue().getClass(), desiredValue);
return null;
}
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) {
ComputedValue inputValue = null;
double exchangeRate = 0;
for (final ComputedValue input : inputs.getAllValues()) {
if (ValueRequirementNames.SPOT_RATE.equals(input.getSpecification().getValueName())) {
if (input.getValue() instanceof Double) {
exchangeRate = (Double) input.getValue();
} else {
return null;
}
} else {
inputValue = input;
}
}
if (inputValue == null) {
return null;
}
final ValueRequirement desiredValue = desiredValues.iterator().next();
final String outputCurrency = desiredValue.getConstraint(ValuePropertyNames.CURRENCY);
final String inputCurrency = inputValue.getSpecification().getProperty(ValuePropertyNames.CURRENCY);
if (outputCurrency.equals(inputCurrency)) {
// Don't think this should happen
return Collections.singleton(inputValue);
} else {
s_logger.debug("Converting from {} to {}", inputCurrency, outputCurrency);
final Object converted = convertValue(inputValue, desiredValue, exchangeRate);
if (converted != null) {
return Collections.singleton(new ComputedValue(new ValueSpecification(desiredValue.getValueName(), target.toSpecification(), desiredValue.getConstraints()), converted));
} else {
return null;
}
}
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final Set<String> possibleCurrencies = desiredValue.getConstraints().getValues(ValuePropertyNames.CURRENCY);
if (possibleCurrencies == null) {
s_logger.debug("Must specify a currency constraint; use DefaultCurrencyFunction instead");
return null;
} else if (possibleCurrencies.isEmpty()) {
if (isAllowViewDefaultCurrency()) {
// The original function may not have delivered a result because it had heterogeneous input currencies, so try forcing the view default
final String defaultCurrencyISO = DefaultCurrencyFunction.getViewDefaultCurrencyISO(context);
if (defaultCurrencyISO == null) {
s_logger.debug("No default currency from the view to inject");
return null;
}
s_logger.debug("Injecting view default currency {}", defaultCurrencyISO);
return Collections.singleton(getInputValueRequirement(target.toSpecification(), desiredValue, defaultCurrencyISO));
} else {
s_logger.debug("Cannot satisfy a wildcard currency constraint");
return null;
}
} else {
// Actual input requirement is desired requirement with the currency wild-carded
return Collections.singleton(getInputValueRequirement(target.toSpecification(), desiredValue));
}
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
// Maximal set of outputs is the valueNames with the infinite property set
final ComputationTargetSpecification targetSpec = target.toSpecification();
if (getValueNames().size() == 1) {
return Collections.singleton(new ValueSpecification(getValueNames().iterator().next(), targetSpec, ValueProperties.all()));
}
final Set<ValueSpecification> result = new HashSet<ValueSpecification>();
for (final String valueName : getValueNames()) {
result.add(new ValueSpecification(valueName, targetSpec, ValueProperties.all()));
}
return result;
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) {
final Map.Entry<ValueSpecification, ValueRequirement> input = inputs.entrySet().iterator().next();
if (input.getValue().getConstraints().getValues(DEFAULT_CURRENCY_INJECTION) == null) {
// Resolved output is the input with the currency wild-carded, and the function ID the same
final ValueSpecification value = input.getKey();
final String currency = value.getProperties().getStrictValue(ValuePropertyNames.CURRENCY);
if (currency == null) {
// This will fail at the getAdditionalRequirements
return null;
}
final ValueProperties.Builder properties = value.getProperties().copy()
.withAny(ValuePropertyNames.CURRENCY)
.withoutAny(ORIGINAL_CURRENCY)
.with(ORIGINAL_CURRENCY, currency)
.with(ValuePropertyNames.CONVERSION_METHOD, CONVERSION_METHOD_VALUE);
return Collections.singleton(new ValueSpecification(value.getValueName(), value.getTargetSpecification(), properties.get()));
}
// The input was requested with the converted currency, so return the same specification to remove this node from the graph
return Collections.singleton(input.getKey());
}
private String getCurrency(final Collection<ValueSpecification> specifications) {
final ValueSpecification specification = specifications.iterator().next();
return specification.getProperties().getStrictValue(ValuePropertyNames.CURRENCY);
}
private ValueRequirement getCurrencyConversion(final String fromCurrency, final String toCurrency) {
return CurrencyMatrixSpotSourcingFunction.getConversionRequirement(fromCurrency, toCurrency);
}
@Override
public Set<ValueRequirement> getAdditionalRequirements(final FunctionCompilationContext context, final ComputationTarget target, final Set<ValueSpecification> inputs,
final Set<ValueSpecification> outputs) {
s_logger.debug("FX requirements for {} -> {}", inputs, outputs);
final String inputCurrency = getCurrency(inputs);
if (inputCurrency == null) {
return null;
}
final String outputCurrency = getCurrency(outputs);
if (outputCurrency == null) {
return null;
}
if (inputCurrency.equals(outputCurrency)) {
// If the input and output currencies are the same then we shouldn't have this node in the graph
return null;
}
return Collections.singleton(getCurrencyConversion(inputCurrency, outputCurrency));
}
@Override
public ComputationTargetType getTargetType() {
return TYPE;
}
}