/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.financial.analytics.model.var; import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.collect.Sets; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.analytics.financial.timeseries.analysis.DoubleTimeSeriesStatisticsCalculator; import com.opengamma.analytics.financial.var.NormalLinearVaRCalculator; import com.opengamma.analytics.financial.var.NormalVaRParameters; import com.opengamma.analytics.financial.var.VaRCalculationResult; import com.opengamma.analytics.math.statistics.descriptive.StatisticsCalculatorFactory; 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.timeseries.HistoricalTimeSeriesFunctionUtils; import com.opengamma.timeseries.DoubleTimeSeries; /** * */ public class NormalHistoricalVaRFunction extends AbstractFunction.NonCompiledInvoker { /** * The name for the normal historical VaR calculation method */ public static final String NORMAL_VAR = "Normal"; /** * The property for the VaR distribution type */ public static final String PROPERTY_VAR_DISTRIBUTION = "VaRDistributionType"; /** * The default PnLContribution property value. */ public static final String DEFAULT_PNL_CONTRIBUTIONS = "Delta"; @Override public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) { final Object pnlSeriesObj = inputs.getValue(ValueRequirementNames.PNL_SERIES); if (pnlSeriesObj == null) { throw new OpenGammaRuntimeException("Could not get P&L series for " + target); } final DoubleTimeSeries<?> pnlSeries = (DoubleTimeSeries<?>) pnlSeriesObj; if (pnlSeries.isEmpty()) { throw new OpenGammaRuntimeException("P&L series for " + target + " was empty"); } // TODO kirk 2012-06-22 -- See TODO below in getResults(). // We assume the constraints are all going to be the same for the results. // Being more restrictive would change this logic and probably not be desirable // but someone other than me should confirm. ValueProperties constraints = null; boolean computeVar = false; boolean computeStddev = false; for (final ValueRequirement desiredValue : desiredValues) { constraints = (constraints == null) ? desiredValue.getConstraints() : constraints; if (ValueRequirementNames.HISTORICAL_VAR.equals(desiredValue.getValueName())) { computeVar = true; } if (ValueRequirementNames.HISTORICAL_VAR_STDDEV.equals(desiredValue.getValueName())) { computeStddev = true; } } final Set<String> scheduleCalculatorNames = constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR); final Set<String> meanCalculatorNames = constraints.getValues(ValuePropertyNames.MEAN_CALCULATOR); final Set<String> stdDevCalculatorNames = constraints.getValues(ValuePropertyNames.STD_DEV_CALCULATOR); final Set<String> confidenceLevelNames = constraints.getValues(ValuePropertyNames.CONFIDENCE_LEVEL); final Set<String> horizonNames = constraints.getValues(ValuePropertyNames.HORIZON); final NormalVaRParameters parameters = getParameters(scheduleCalculatorNames, horizonNames, confidenceLevelNames); final NormalLinearVaRCalculator<DoubleTimeSeries<?>> varCalculator = getVaRCalculator(meanCalculatorNames, stdDevCalculatorNames); final VaRCalculationResult calcResult = varCalculator.evaluate(parameters, pnlSeries); final double var = calcResult.getVaRValue(); final double stddev = calcResult.getStdDev(); final Set<ComputedValue> results = new HashSet<ComputedValue>(); if (computeVar) { results.add(new ComputedValue(new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR, target.toSpecification(), constraints), var)); } if (computeStddev) { results.add(new ComputedValue(new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR_STDDEV, target.toSpecification(), constraints), stddev)); } return results; } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) { final ValueProperties properties = createValueProperties() .withAny(ValuePropertyNames.CURRENCY) .withAny(ValuePropertyNames.SAMPLING_PERIOD) .withAny(ValuePropertyNames.SCHEDULE_CALCULATOR) .withAny(ValuePropertyNames.SAMPLING_FUNCTION) .withAny(ValuePropertyNames.MEAN_CALCULATOR) .withAny(ValuePropertyNames.STD_DEV_CALCULATOR) .withAny(ValuePropertyNames.CONFIDENCE_LEVEL) .withAny(ValuePropertyNames.HORIZON) .withAny(ValuePropertyNames.AGGREGATION) .withAny(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS) .with(PROPERTY_VAR_DISTRIBUTION, NORMAL_VAR).get(); final ValueSpecification hVaRSpec = new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR, target.toSpecification(), properties); // TODO kirk 2012-06-22 -- These are certainly not the optimal properties. Rather, // things like CONFIDENCE_LEVEL actually depend on the stddev, so this doesn't make // 100% of sense. However, in an effort to make this the simplest addition possible // I'm just reusing the identical properties. final ValueSpecification stddevSpec = new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR_STDDEV, target.toSpecification(), properties); return Sets.newHashSet(hVaRSpec, stddevSpec); } @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.size() != 1) { return null; } final Set<String> scheduleCalculatorName = constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR); if (scheduleCalculatorName == null || scheduleCalculatorName.size() != 1) { return null; } final Set<String> samplingFunctionName = constraints.getValues(ValuePropertyNames.SAMPLING_FUNCTION); if (samplingFunctionName == null || samplingFunctionName.size() != 1) { return null; } final Set<String> meanCalculatorName = constraints.getValues(ValuePropertyNames.MEAN_CALCULATOR); if (meanCalculatorName == null || meanCalculatorName.size() != 1) { return null; } final Set<String> stdDevCalculatorName = constraints.getValues(ValuePropertyNames.STD_DEV_CALCULATOR); if (stdDevCalculatorName == null || stdDevCalculatorName.size() != 1) { return null; } final Set<String> pnlContributionNames = constraints.getValues(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS); if (pnlContributionNames != null && pnlContributionNames.size() != 1) { return null; } String pnlContributionName = pnlContributionNames != null ? pnlContributionNames.iterator().next() : DEFAULT_PNL_CONTRIBUTIONS; final ValueProperties.Builder properties = ValueProperties.builder() .with(ValuePropertyNames.SCHEDULE_CALCULATOR, scheduleCalculatorName.iterator().next()) .with(ValuePropertyNames.SAMPLING_FUNCTION, samplingFunctionName.iterator().next()) .with(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS, pnlContributionName); //TODO if (desiredValue.getConstraint(HistoricalTimeSeriesFunctionUtils.START_DATE_PROPERTY) == null) { properties.with(ValuePropertyNames.SAMPLING_PERIOD, samplingPeriodName.iterator().next()); } copyOptional(desiredValue.getConstraints(), properties); final Set<String> desiredCurrencyValues = desiredValue.getConstraints().getValues(ValuePropertyNames.CURRENCY); if (desiredCurrencyValues == null || desiredCurrencyValues.isEmpty()) { properties.withAny(ValuePropertyNames.CURRENCY); } else { properties.with(ValuePropertyNames.CURRENCY, desiredCurrencyValues); } final Set<String> aggregationStyle = constraints.getValues(ValuePropertyNames.AGGREGATION); if (aggregationStyle != null) { if (aggregationStyle.isEmpty()) { properties.withOptional(ValuePropertyNames.AGGREGATION); } else { if (constraints.isOptional(ValuePropertyNames.AGGREGATION)) { properties.withOptional(ValuePropertyNames.AGGREGATION); } properties.with(ValuePropertyNames.AGGREGATION, aggregationStyle); } } return Collections.singleton(new ValueRequirement(ValueRequirementNames.PNL_SERIES, target.toSpecification(), properties.get())); } @Override public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) { final ValueSpecification input = inputs.keySet().iterator().next(); final String currency = input.getProperty(ValuePropertyNames.CURRENCY); if (currency == null) { return null; } final ValueProperties properties = getResultProperties( input.getProperties(), currency, input.getProperty(ValuePropertyNames.AGGREGATION), input.getProperty(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS)); // see note above in other getResults(). final ValueSpecification varSpecification = new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR, target.toSpecification(), properties); final ValueSpecification stddevSpecification = new ValueSpecification(ValueRequirementNames.HISTORICAL_VAR_STDDEV, target.toSpecification(), properties); return Sets.newHashSet(varSpecification, stddevSpecification); } private ValueProperties getResultProperties(ValueProperties priceTsProperties, final String currency, final String aggregationStyle, final String pnlContribution) { final ValueProperties.Builder properties = createValueProperties() .with(ValuePropertyNames.CURRENCY, currency) .withAny(ValuePropertyNames.SAMPLING_PERIOD) .withAny(ValuePropertyNames.SCHEDULE_CALCULATOR) .withAny(ValuePropertyNames.SAMPLING_FUNCTION) .withAny(ValuePropertyNames.MEAN_CALCULATOR) .withAny(ValuePropertyNames.STD_DEV_CALCULATOR) .withAny(ValuePropertyNames.CONFIDENCE_LEVEL) .withAny(ValuePropertyNames.HORIZON) .with(ValuePropertyNames.PROPERTY_PNL_CONTRIBUTIONS, pnlContribution) .with(PROPERTY_VAR_DISTRIBUTION, NORMAL_VAR); copyOptional(priceTsProperties, properties); if (aggregationStyle != null) { properties.with(ValuePropertyNames.AGGREGATION, aggregationStyle); } return properties.get(); } private NormalVaRParameters getParameters(final Set<String> scheduleCalculatorNames, final Set<String> horizonNames, final Set<String> confidenceLevelNames) { if (scheduleCalculatorNames == null || scheduleCalculatorNames.isEmpty() || scheduleCalculatorNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique schedule calculator name: " + scheduleCalculatorNames); } if (horizonNames == null || horizonNames.isEmpty() || horizonNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique horizon name: " + horizonNames); } if (confidenceLevelNames == null || confidenceLevelNames.isEmpty() || confidenceLevelNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique confidence level name: " + confidenceLevelNames); } return new NormalVaRParameters(Double.valueOf(horizonNames.iterator().next()), VaRFunctionUtils.getBusinessDaysPerPeriod(scheduleCalculatorNames.iterator().next()), Double.valueOf(confidenceLevelNames.iterator().next())); } private NormalLinearVaRCalculator<DoubleTimeSeries<?>> getVaRCalculator(final Set<String> meanCalculatorNames, final Set<String> stdDevCalculatorNames) { if (meanCalculatorNames == null || meanCalculatorNames.isEmpty() || meanCalculatorNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique mean calculator name: " + meanCalculatorNames); } if (stdDevCalculatorNames == null || stdDevCalculatorNames.isEmpty() || stdDevCalculatorNames.size() != 1) { throw new OpenGammaRuntimeException("Missing or non-unique standard deviation calculator name: " + stdDevCalculatorNames); } final DoubleTimeSeriesStatisticsCalculator meanCalculator = new DoubleTimeSeriesStatisticsCalculator(StatisticsCalculatorFactory.getCalculator(meanCalculatorNames.iterator().next())); final DoubleTimeSeriesStatisticsCalculator stdDevCalculator = new DoubleTimeSeriesStatisticsCalculator(StatisticsCalculatorFactory.getCalculator(stdDevCalculatorNames.iterator().next())); return new NormalLinearVaRCalculator<DoubleTimeSeries<?>>(meanCalculator, stdDevCalculator); } private ValueProperties.Builder copyOptional(ValueProperties origProps, ValueProperties.Builder propBuilder) { for (String prop: origProps.getProperties()) { if (origProps.isOptional(prop)) { propBuilder.withOptional(prop).with(prop, origProps.getSingleValue(prop)); } } return propBuilder; } @Override public ComputationTargetType getTargetType() { return ComputationTargetType.PORTFOLIO_NODE.or(ComputationTargetType.POSITION); } }