/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.timeseries; import java.util.Collections; import java.util.Map; import java.util.Set; import org.threeten.bp.LocalDate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.schedule.HolidayDateRemovalFunction; import com.opengamma.analytics.financial.schedule.Schedule; import com.opengamma.analytics.financial.schedule.ScheduleCalculatorFactory; import com.opengamma.analytics.financial.schedule.TimeSeriesSamplingFunction; import com.opengamma.analytics.financial.schedule.TimeSeriesSamplingFunctionFactory; import com.opengamma.analytics.financial.timeseries.util.TimeSeriesDifferenceOperator; import com.opengamma.analytics.financial.timeseries.util.TimeSeriesPercentageChangeOperator; 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.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.forex.ConventionBasedFXRateFunction; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.calendar.MondayToFridayCalendar; import com.opengamma.timeseries.date.DateDoubleTimeSeries; import com.opengamma.timeseries.date.localdate.LocalDateDoubleTimeSeries; import com.opengamma.util.async.AsynchronousExecution; import com.opengamma.util.money.UnorderedCurrencyPair; /** * Calculates an absolute return series from a time-series of FX spot rates. */ public class FXReturnSeriesFunction extends AbstractFunction.NonCompiledInvoker { /** Absolute returns */ public static final String ABSOLUTE_RETURNS = "Absolute"; /** Relative returns */ public static final String RELATIVE_RETURNS = "Relative"; /** Removes weekends */ private static final HolidayDateRemovalFunction HOLIDAY_REMOVER = HolidayDateRemovalFunction.getInstance(); /** A weekend calendar */ private static final Calendar WEEKEND_CALENDAR = new MondayToFridayCalendar("Weekend"); /** Calculates an absolute return time series */ private static final TimeSeriesDifferenceOperator DIFFERENCE = new TimeSeriesDifferenceOperator(); /** Calculates a relative return time series */ private static final TimeSeriesPercentageChangeOperator PERCENTAGE_CHANGE = new TimeSeriesPercentageChangeOperator(); @Override public ComputationTargetType getTargetType() { return ComputationTargetType.UNORDERED_CURRENCY_PAIR; } /** * Gets the result properties for an FX return series with no transformation method set. * @return The result properties */ protected ValueProperties getResultProperties() { final ValueProperties properties = createValueProperties() .withAny(ValuePropertyNames.SAMPLING_FUNCTION) .withAny(ValuePropertyNames.SCHEDULE_CALCULATOR) .withAny(ValuePropertyNames.RETURN_CALCULATOR) .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) .with(ValuePropertyNames.TRANSFORMATION_METHOD, "None") .get(); return properties; } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) { return ImmutableSet.of(new ValueSpecification(ValueRequirementNames.RETURN_SERIES, target.toSpecification(), ValueProperties.all())); } @Override public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) { final ValueProperties constraints = desiredValue.getConstraints(); final String spotSeriesStart = getSpotSeriesStart(constraints); if (spotSeriesStart == null) { return null; } final DateConstraint start = DateConstraint.parse(spotSeriesStart); final String returnSeriesEnd = getReturnSeriesEnd(constraints); if (returnSeriesEnd == null) { return null; } final DateConstraint end = DateConstraint.parse(returnSeriesEnd); final Set<String> includeStarts = constraints.getValues(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY); if (includeStarts != null && includeStarts.size() != 1) { return null; } final boolean includeStart = includeStarts == null ? true : HistoricalTimeSeriesFunctionUtils.YES_VALUE.equals(Iterables.getOnlyElement(includeStarts)); final Set<String> includeEnds = constraints.getValues(HistoricalTimeSeriesFunctionUtils.INCLUDE_END_PROPERTY); if (includeEnds != null && includeEnds.size() != 1) { return null; } final boolean includeEnd = includeEnds == null ? false : HistoricalTimeSeriesFunctionUtils.YES_VALUE.equals(Iterables.getOnlyElement(includeEnds)); final Set<String> samplingMethod = constraints.getValues(ValuePropertyNames.SAMPLING_FUNCTION); if (samplingMethod == null || samplingMethod.size() != 1) { return null; } final Set<String> scheduleMethod = constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR); if (scheduleMethod == null || scheduleMethod.size() != 1) { return null; } final Set<String> returnMethods = constraints.getValues(ValuePropertyNames.RETURN_CALCULATOR); if (returnMethods == null || returnMethods.size() != 1) { final ValueProperties newConstraints = constraints.copy() .withoutAny(ValuePropertyNames.RETURN_CALCULATOR) .with(ValuePropertyNames.RETURN_CALCULATOR, ABSOLUTE_RETURNS) .get(); return Collections.singleton(new ValueRequirement(ValueRequirementNames.RETURN_SERIES, target.toSpecification(), newConstraints)); } return ImmutableSet.of(ConventionBasedFXRateFunction.getHistoricalTimeSeriesRequirement((UnorderedCurrencyPair) target.getValue(), start, includeStart, end, includeEnd)); } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) { final ValueSpecification input = inputs.keySet().iterator().next(); if (ValueRequirementNames.RETURN_SERIES.equals(input.getValueName())) { return Collections.singleton(input); } return ImmutableSet.of(new ValueSpecification(ValueRequirementNames.RETURN_SERIES, target.toSpecification(), getResultProperties())); } @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) throws AsynchronousExecution { final ValueRequirement desiredValue = desiredValues.iterator().next(); final ComputedValue timeSeriesValue = inputs.getComputedValue(ValueRequirementNames.HISTORICAL_FX_TIME_SERIES); final DateDoubleTimeSeries<?> timeSeries = (DateDoubleTimeSeries<?>) timeSeriesValue.getValue(); final boolean includeStart = HistoricalTimeSeriesFunctionUtils.parseBoolean(timeSeriesValue.getSpecification().getProperty(HistoricalTimeSeriesFunctionUtils.INCLUDE_START_PROPERTY)); final LocalDate spotSeriesStart = DateConstraint.evaluate(executionContext, getSpotSeriesStart(desiredValue.getConstraints())); final LocalDate returnSeriesStart = DateConstraint.evaluate(executionContext, getReturnSeriesStart(desiredValue.getConstraints())); if (spotSeriesStart.isAfter(returnSeriesStart)) { throw new OpenGammaRuntimeException("Return series start date cannot be before spot series start date"); } final LocalDate returnSeriesEnd = DateConstraint.evaluate(executionContext, getReturnSeriesEnd(desiredValue.getConstraints())); final String scheduleCalculatorName = desiredValue.getConstraint(ValuePropertyNames.SCHEDULE_CALCULATOR); final String samplingFunctionName = desiredValue.getConstraint(ValuePropertyNames.SAMPLING_FUNCTION); final Schedule scheduleCalculator = ScheduleCalculatorFactory.getScheduleCalculator(scheduleCalculatorName); final TimeSeriesSamplingFunction samplingFunction = TimeSeriesSamplingFunctionFactory.getFunction(samplingFunctionName); final LocalDate[] dates = HOLIDAY_REMOVER.getStrippedSchedule(scheduleCalculator.getSchedule(spotSeriesStart, returnSeriesEnd, true, false), WEEKEND_CALENDAR); LocalDateDoubleTimeSeries sampledTimeSeries = samplingFunction.getSampledTimeSeries(timeSeries, dates); sampledTimeSeries = sampledTimeSeries.reciprocal(); // Implementation note: to obtain the series for one unit of non-base currency expressed in base currency. LocalDateDoubleTimeSeries returnSeries = getReturnSeries(sampledTimeSeries, desiredValue); // Clip the time-series to the range originally asked for returnSeries = returnSeries.subSeries(returnSeriesStart, includeStart, returnSeries.getLatestTime(), true); return ImmutableSet.of(new ComputedValue(new ValueSpecification(ValueRequirementNames.RETURN_SERIES, target.toSpecification(), desiredValue.getConstraints()), returnSeries)); } /** * Gets the spot series start date from the desired value constraints. * @param constraints The constraints * @return The spot series start date */ protected String getSpotSeriesStart(final ValueProperties constraints) { return getReturnSeriesStart(constraints); } /** * Gets the return series start date from the desired value constraints. * @param constraints The constraints * @return The return series start date */ protected String getReturnSeriesStart(final ValueProperties constraints) { final Set<String> startDates = constraints.getValues(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY); if (startDates == null || startDates.size() != 1) { return null; } return Iterables.getOnlyElement(startDates); } /** * Gets the return series end date from the desired value constraints. * @param constraints The constraints * @return The return series end date */ protected String getReturnSeriesEnd(final ValueProperties constraints) { final Set<String> endDates = constraints.getValues(HistoricalTimeSeriesFunctionUtils.END_DATE_PROPERTY); if (endDates == null || endDates.size() != 1) { return null; } return Iterables.getOnlyElement(endDates); } /** * Gets the return series from the spot series. * @param spotSeries The spot FX series * @param desiredValue The desired return series requirement * @return The FX return series */ protected LocalDateDoubleTimeSeries getReturnSeries(final LocalDateDoubleTimeSeries spotSeries, final ValueRequirement desiredValue) { final ValueProperties constraints = desiredValue.getConstraints(); final Set<String> returnProperty = constraints.getValues(ValuePropertyNames.RETURN_CALCULATOR); if (returnProperty != null && returnProperty.size() == 1 && Iterables.getOnlyElement(returnProperty).equals(RELATIVE_RETURNS)) { return (LocalDateDoubleTimeSeries) PERCENTAGE_CHANGE.evaluate(spotSeries); } return (LocalDateDoubleTimeSeries) DIFFERENCE.evaluate(spotSeries); } }