/** * 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.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.threeten.bp.LocalDate; import org.threeten.bp.Period; import com.google.common.collect.Sets; 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.core.security.SecuritySource; 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.ValuePropertyNames; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueRequirementNames; import com.opengamma.engine.value.ValueSpecification; import com.opengamma.financial.OpenGammaCompilationContext; 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.calendar.Calendar; import com.opengamma.financial.convention.calendar.MondayToFridayCalendar; import com.opengamma.financial.security.FinancialSecurityUtils; import com.opengamma.financial.sensitivities.FactorExposureData; import com.opengamma.financial.sensitivities.RawSecurityUtils; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolver; import com.opengamma.master.security.RawSecurity; import com.opengamma.timeseries.DoubleTimeSeries; import com.opengamma.timeseries.date.DateDoubleTimeSeries; import com.opengamma.timeseries.date.localdate.ImmutableLocalDateDoubleTimeSeries; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.money.Currency; /** * */ public class ExternallyProvidedSensitivityPnLFunction extends AbstractFunction.NonCompiledInvoker { private static final Logger s_logger = LoggerFactory.getLogger(ExternallyProvidedSensitivityPnLFunction.class); private static final HolidayDateRemovalFunction HOLIDAY_REMOVER = HolidayDateRemovalFunction.getInstance(); private static final Calendar WEEKEND_CALENDAR = new MondayToFridayCalendar("Weekend"); private static final TimeSeriesDifferenceOperator DIFFERENCE = new TimeSeriesDifferenceOperator(); private static final LocalDate MAGIC_DATE = LocalDate.of(2009, 06, 05); private final String _resolutionKey; public ExternallyProvidedSensitivityPnLFunction(final String resolutionKey) { ArgumentChecker.notNull(resolutionKey, "resolution key"); _resolutionKey = resolutionKey; } @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) { final Position position = target.getPosition(); final ComputationTargetSpecification positionSpec = target.toSpecification(); final RawSecurity security = (RawSecurity) position.getSecurity(); final SecuritySource secSource = executionContext.getSecuritySource(); //final Clock snapshotClock = executionContext.getValuationClock(); final LocalDate now = MAGIC_DATE; //ZonedDateTime.now(snapshotClock).getDate(); final Currency currency = FinancialSecurityUtils.getCurrency(position.getSecurity()); final String currencyString = currency.getCode(); final ValueRequirement desiredValue = desiredValues.iterator().next(); final ValueProperties constraints = desiredValue.getConstraints(); final Period samplingPeriod = getSamplingPeriod(constraints.getValues(ValuePropertyNames.SAMPLING_PERIOD)); final LocalDate startDate = now.minus(samplingPeriod); final Schedule scheduleCalculator = getScheduleCalculator(constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR)); final TimeSeriesSamplingFunction samplingFunction = getSamplingFunction(constraints.getValues(ValuePropertyNames.SAMPLING_FUNCTION)); final LocalDate[] schedule = HOLIDAY_REMOVER.getStrippedSchedule(scheduleCalculator.getSchedule(startDate, now, true, false), WEEKEND_CALENDAR); //REVIEW emcleod should "fromEnd" be hard-coded? final HistoricalTimeSeriesBundle timeSeries = HistoricalTimeSeriesFunctionUtils.getHistoricalTimeSeriesInputs(executionContext, inputs); DoubleTimeSeries<?> result = getPnLSeries(security, secSource, timeSeries, inputs, startDate, now, schedule, samplingFunction); result = result.multiply(position.getQuantity().doubleValue()); final ValueProperties resultProperties = getResultProperties(desiredValue, currencyString); final ValueSpecification resultSpec = new ValueSpecification(ValueRequirementNames.PNL_SERIES, positionSpec, resultProperties); return Sets.newHashSet(new ComputedValue(resultSpec, result)); } @Override public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) { final Security security = target.getPosition().getSecurity(); return RawSecurityUtils.isExternallyProvidedSensitivitiesSecurity(security); } @Override public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) { final RawSecurity security = (RawSecurity) target.getPosition().getSecurity(); final String samplingPeriod = desiredValue.getConstraint(ValuePropertyNames.SAMPLING_PERIOD); final Set<ValueRequirement> sensitivityRequirements = getSensitivityRequirements(OpenGammaCompilationContext.getHistoricalTimeSeriesResolver(context), context.getSecuritySource(), security, samplingPeriod); return sensitivityRequirements; } @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) .get(); return Collections.singleton(new ValueSpecification(ValueRequirementNames.PNL_SERIES, target.toSpecification(), properties)); } @Override public ComputationTargetType getTargetType() { return ComputationTargetType.POSITION; } private ValueProperties getResultProperties(final ValueRequirement desiredValue, final String currency) { return createValueProperties() .with(ValuePropertyNames.CURRENCY, currency) .with(ValuePropertyNames.SAMPLING_PERIOD, desiredValue.getConstraint(ValuePropertyNames.SAMPLING_PERIOD)) .with(ValuePropertyNames.SCHEDULE_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.SCHEDULE_CALCULATOR)) .with(ValuePropertyNames.SAMPLING_FUNCTION, desiredValue.getConstraint(ValuePropertyNames.SAMPLING_FUNCTION)) .get(); } private String getPropertyName(final Set<String> propertyName) { if (propertyName == null || propertyName.isEmpty() || propertyName.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique property name: " + propertyName); } return propertyName.iterator().next(); } private Period getSamplingPeriod(final Set<String> samplingPeriodNames) { final String samplingPeriodName = getPropertyName(samplingPeriodNames); return Period.parse(samplingPeriodName); } private Schedule getScheduleCalculator(final Set<String> scheduleCalculatorNames) { final String scheduleCalculatorName = getPropertyName(scheduleCalculatorNames); return ScheduleCalculatorFactory.getScheduleCalculator(scheduleCalculatorName); } private TimeSeriesSamplingFunction getSamplingFunction(final Set<String> samplingFunctionNames) { final String samplingFunctionName = getPropertyName(samplingFunctionNames); return TimeSeriesSamplingFunctionFactory.getFunction(samplingFunctionName); } private DoubleTimeSeries<?> getPnLSeries(final RawSecurity security, final SecuritySource secSource, final HistoricalTimeSeriesBundle timeSeries, final FunctionInputs inputs, final LocalDate startDate, final LocalDate now, final LocalDate[] schedule, final TimeSeriesSamplingFunction samplingFunction) { DoubleTimeSeries<?> pnlSeries = null; final List<FactorExposureData> factors = RawSecurityUtils.decodeFactorExposureData(secSource, security); for (final FactorExposureData factor : factors) { final HistoricalTimeSeries dbNodeTimeSeries = timeSeries.get("PX_LAST", factor.getFactorExternalId()); if (dbNodeTimeSeries == null || dbNodeTimeSeries.getTimeSeries().size() == 0) { //s_logger.warn("Could not identifier / price series pair for " + id + " for " + _resolutionKey + "/PX_LAST"); //throw new OpenGammaRuntimeException("Could not identifier / price series pair for " + id + " for " + _resolutionKey + "/PX_LAST"); continue; } DateDoubleTimeSeries<?> nodeTimeSeries = samplingFunction.getSampledTimeSeries(dbNodeTimeSeries.getTimeSeries(), schedule); nodeTimeSeries = DIFFERENCE.evaluate(nodeTimeSeries); final Double sensitivity = (Double) inputs.getValue(getSensitivityRequirement(factor.getExposureExternalId())); if (sensitivity != null) { if (pnlSeries == null) { pnlSeries = nodeTimeSeries.multiply(sensitivity / 100d); } else { pnlSeries = pnlSeries.add(nodeTimeSeries.multiply(sensitivity / 100d)); } } else { s_logger.warn("Could not get sensitivity " + factor.getExposureExternalId()); } } if (pnlSeries == null) { final List<LocalDate> dates = new ArrayList<LocalDate>(); final List<Double> values = new ArrayList<Double>(); dates.add(MAGIC_DATE.minusDays(7)); dates.add(MAGIC_DATE); values.add(0d); values.add(0d); pnlSeries = ImmutableLocalDateDoubleTimeSeries.of(dates, values); } return pnlSeries; } // private double getNormalizationFactor(final StripInstrumentType type) { // switch(type) { // case TENOR_SWAP: // return 10000.; // case BASIS_SWAP: // return 10000.; // case OIS_SWAP: // return 10000.; // default: // return 100.; // } // } protected Set<ValueRequirement> getSensitivityRequirements(final HistoricalTimeSeriesResolver resolver, final SecuritySource secSource, final RawSecurity rawSecurity, final String samplingPeriod) { final Set<ValueRequirement> requirements = Sets.newHashSet(); final Collection<FactorExposureData> decodedSensitivities = RawSecurityUtils.decodeFactorExposureData(secSource, rawSecurity); for (final FactorExposureData exposureEntry : decodedSensitivities) { requirements.add(getSensitivityRequirement(exposureEntry.getExposureExternalId())); requirements.add(getTimeSeriesRequirement(resolver, exposureEntry.getFactorExternalId().toBundle(), samplingPeriod)); } return requirements; } protected ValueRequirement getSensitivityRequirement(final ExternalId externalId) { return new ValueRequirement(/*ExternalDataRequirementNames.SENSITIVITY*/"EXPOSURE", ComputationTargetType.PRIMITIVE, externalId); } protected ValueRequirement getTimeSeriesRequirement(final HistoricalTimeSeriesResolver resolver, final ExternalIdBundle bundle, final String samplingPeriod) { return HistoricalTimeSeriesFunctionUtils.createHTSRequirement(resolver.resolve(bundle, null, null, null, "PX_LAST", _resolutionKey), "PX_LAST", DateConstraint.of(MAGIC_DATE.minus(Period.parse(samplingPeriod))), true, DateConstraint.of(MAGIC_DATE), true); } }