/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.model.pnl; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.commons.lang.Validate; import org.threeten.bp.Clock; import org.threeten.bp.LocalDate; import org.threeten.bp.Period; import org.threeten.bp.ZonedDateTime; 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.core.historicaltimeseries.HistoricalTimeSeries; import com.opengamma.core.position.Position; import com.opengamma.core.security.Security; 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.OpenGammaExecutionContext; import com.opengamma.financial.analytics.model.forex.ConventionBasedFXRateFunction; import com.opengamma.financial.analytics.timeseries.DateConstraint; import com.opengamma.financial.convention.calendar.Calendar; import com.opengamma.financial.convention.calendar.MondayToFridayCalendar; import com.opengamma.financial.currency.CurrencyPair; import com.opengamma.financial.currency.CurrencyPairs; import com.opengamma.financial.security.FinancialSecurityUtils; import com.opengamma.financial.security.future.FXFutureSecurity; import com.opengamma.timeseries.DoubleTimeSeries; import com.opengamma.timeseries.date.DateDoubleTimeSeries; import com.opengamma.util.money.Currency; /** * */ public class SimpleFXFuturePnLFunction extends AbstractFunction.NonCompiledInvoker { private static final HolidayDateRemovalFunction HOLIDAY_REMOVER = HolidayDateRemovalFunction.getInstance(); private static final Calendar WEEKEND_CALENDAR = new MondayToFridayCalendar("Weekend"); private static final TimeSeriesDifferenceOperator DIFFERENCE = new TimeSeriesDifferenceOperator(); // TODO: The resolution key isn't used public SimpleFXFuturePnLFunction(final String resolutionKey) { Validate.notNull(resolutionKey, "resolution key"); } @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) { final Position position = target.getPosition(); final FXFutureSecurity security = (FXFutureSecurity) position.getSecurity(); final Clock snapshotClock = executionContext.getValuationClock(); final LocalDate now = ZonedDateTime.now(snapshotClock).toLocalDate(); final ValueRequirement desiredValue = desiredValues.iterator().next(); final Set<String> samplingPeriodName = desiredValue.getConstraints().getValues(ValuePropertyNames.SAMPLING_PERIOD); final Set<String> scheduleCalculatorName = desiredValue.getConstraints().getValues(ValuePropertyNames.SCHEDULE_CALCULATOR); final Set<String> samplingFunctionName = desiredValue.getConstraints().getValues(ValuePropertyNames.SAMPLING_FUNCTION); final Period samplingPeriod = getSamplingPeriod(samplingPeriodName); final LocalDate startDate = now.minus(samplingPeriod); final Currency payCurrency = security.getNumerator(); final Currency receiveCurrency = security.getDenominator(); final HistoricalTimeSeries dbTimeSeries = (HistoricalTimeSeries) inputs.getValue(ValueRequirementNames.HISTORICAL_TIME_SERIES); if (dbTimeSeries == null) { throw new OpenGammaRuntimeException("Could not get identifier / price series pair for " + security); } DoubleTimeSeries<?> ts = dbTimeSeries.getTimeSeries(); if (ts == null) { throw new OpenGammaRuntimeException("Could not get price series for " + security); } if (ts.isEmpty()) { throw new OpenGammaRuntimeException("Empty price series for " + security); } // TODO: If we know which way up we want the time series, don't request it in "convention order" and then lookup the convention again here, request it in // the desired order in getRequirements using a CurrencyPair final CurrencyPairs currencyPairs = OpenGammaExecutionContext.getCurrencyPairsSource(executionContext).getCurrencyPairs(CurrencyPairs.DEFAULT_CURRENCY_PAIRS); final CurrencyPair currencyPair = currencyPairs.getCurrencyPair(security.getNumerator(), security.getDenominator()); if (!payCurrency.equals(currencyPair.getBase()) && receiveCurrency.equals(security.getCurrency())) { ts = ts.reciprocal(); } final Object pvObject = inputs.getValue(new ValueRequirement(ValueRequirementNames.PRESENT_VALUE, ComputationTargetType.SECURITY, security.getUniqueId())); if (pvObject == null) { throw new OpenGammaRuntimeException("Present value was null"); } final double pv = (Double) pvObject; final Schedule scheduleCalculator = getScheduleCalculator(scheduleCalculatorName); final TimeSeriesSamplingFunction samplingFunction = getSamplingFunction(samplingFunctionName); final LocalDate[] schedule = HOLIDAY_REMOVER.getStrippedSchedule(scheduleCalculator.getSchedule(startDate, now, true, false), WEEKEND_CALENDAR); //REVIEW emcleod should "fromEnd" be hard-coded? DateDoubleTimeSeries<?> pnlSeries = samplingFunction.getSampledTimeSeries(dbTimeSeries.getTimeSeries(), schedule); pnlSeries = DIFFERENCE.evaluate(pnlSeries); pnlSeries = pnlSeries.multiply(pv); final ValueSpecification spec = new ValueSpecification(ValueRequirementNames.PNL_SERIES, target.toSpecification(), desiredValue.getConstraints()); return Collections.singleton(new ComputedValue(spec, pnlSeries)); } @Override public ComputationTargetType getTargetType() { return ComputationTargetType.POSITION; } @Override public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) { final Position position = target.getPosition(); final Security security = position.getSecurity(); return security instanceof FXFutureSecurity; } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) { final Position position = target.getPosition(); final ValueProperties properties = createValueProperties() .with(ValuePropertyNames.CURRENCY, FinancialSecurityUtils.getCurrency(position.getSecurity()).getCode()) .withAny(ValuePropertyNames.SAMPLING_PERIOD) .withAny(ValuePropertyNames.SCHEDULE_CALCULATOR) .withAny(ValuePropertyNames.SAMPLING_FUNCTION) .withAny(ValuePropertyNames.PAY_CURVE) .withAny(ValuePropertyNames.RECEIVE_CURVE) .get(); return Collections.singleton(new ValueSpecification(ValueRequirementNames.PNL_SERIES, target.toSpecification(), properties)); } @Override public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) { final ValueProperties constraints = desiredValue.getConstraints(); final Set<String> samplingPeriodName = constraints.getValues(ValuePropertyNames.SAMPLING_PERIOD); if (samplingPeriodName == null || samplingPeriodName.isEmpty() || samplingPeriodName.size() != 1) { return null; } final Set<String> scheduleCalculatorName = constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR); if (scheduleCalculatorName == null || scheduleCalculatorName.isEmpty() || scheduleCalculatorName.size() != 1) { return null; } final Set<String> samplingFunctionName = constraints.getValues(ValuePropertyNames.SAMPLING_FUNCTION); if (samplingFunctionName == null || samplingFunctionName.isEmpty() || samplingFunctionName.size() != 1) { return null; } final Set<String> payCurveName = constraints.getValues(ValuePropertyNames.PAY_CURVE); if (payCurveName == null || payCurveName.isEmpty() || payCurveName.size() != 1) { return null; } final Set<String> receiveCurveName = constraints.getValues(ValuePropertyNames.RECEIVE_CURVE); if (receiveCurveName == null || receiveCurveName.isEmpty() || receiveCurveName.size() != 1) { return null; } final Position position = target.getPosition(); final FXFutureSecurity future = (FXFutureSecurity) position.getSecurity(); final Set<ValueRequirement> requirements = new HashSet<ValueRequirement>(); final ValueProperties pvProperties = ValueProperties.builder() .with(ValuePropertyNames.CURRENCY, future.getCurrency().getCode()) .with(ValuePropertyNames.PAY_CURVE, payCurveName) .with(ValuePropertyNames.RECEIVE_CURVE, receiveCurveName).get(); requirements.add(new ValueRequirement(ValueRequirementNames.PRESENT_VALUE, ComputationTargetType.SECURITY, future.getUniqueId(), pvProperties)); requirements.add(ConventionBasedFXRateFunction.getHistoricalTimeSeriesRequirement(future.getNumerator(), future.getDenominator(), DateConstraint.VALUATION_TIME.minus(samplingPeriodName.iterator().next()), true, DateConstraint.VALUATION_TIME, true)); return requirements; } private Period getSamplingPeriod(final Set<String> samplingPeriodNames) { if (samplingPeriodNames == null || samplingPeriodNames.isEmpty() || samplingPeriodNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique sampling period name: " + samplingPeriodNames); } return Period.parse(samplingPeriodNames.iterator().next()); } private Schedule getScheduleCalculator(final Set<String> scheduleCalculatorNames) { if (scheduleCalculatorNames == null || scheduleCalculatorNames.isEmpty() || scheduleCalculatorNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique schedule calculator name: " + scheduleCalculatorNames); } return ScheduleCalculatorFactory.getScheduleCalculator(scheduleCalculatorNames.iterator().next()); } private TimeSeriesSamplingFunction getSamplingFunction(final Set<String> samplingFunctionNames) { if (samplingFunctionNames == null || samplingFunctionNames.isEmpty() || samplingFunctionNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique sampling function name: " + samplingFunctionNames); } return TimeSeriesSamplingFunctionFactory.getFunction(samplingFunctionNames.iterator().next()); } }