/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics.model.equity.portfoliotheory;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.Validate;
import org.threeten.bp.Period;
import com.google.common.collect.Sets;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.analytics.financial.riskreward.TreynorRatioCalculator;
import com.opengamma.analytics.financial.timeseries.analysis.DoubleTimeSeriesStatisticsCalculator;
import com.opengamma.analytics.math.function.Function;
import com.opengamma.analytics.math.statistics.descriptive.StatisticsCalculatorFactory;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeries;
import com.opengamma.core.value.MarketDataRequirementNames;
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.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.HistoricalTimeSeriesFunctionUtils;
import com.opengamma.financial.convention.ConventionBundle;
import com.opengamma.financial.convention.ConventionBundleSource;
import com.opengamma.financial.convention.InMemoryConventionBundleMaster;
import com.opengamma.id.ExternalId;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolutionResult;
import com.opengamma.master.historicaltimeseries.HistoricalTimeSeriesResolver;
import com.opengamma.timeseries.DoubleTimeSeries;
import com.opengamma.timeseries.TimeSeriesIntersector;
/**
*
*/
public abstract class TreynorRatioFunction extends AbstractFunction.NonCompiledInvoker {
private static final double DAYS_PER_YEAR = 365.25; //TODO
private final String _resolutionKey;
public TreynorRatioFunction(final String resolutionKey) {
Validate.notNull(resolutionKey, "resolution key");
_resolutionKey = resolutionKey;
}
@Override
public boolean canApplyTo(final FunctionCompilationContext compilationContext, final ComputationTarget target) {
return true;
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target,
final Set<ValueRequirement> desiredValues) {
final ComputationTargetSpecification targetSpec = target.toSpecification();
final ValueRequirement desiredValue = desiredValues.iterator().next();
final ValueProperties constraints = desiredValue.getConstraints();
final HistoricalTimeSeries riskFreeRateTSObject = (HistoricalTimeSeries) inputs.getValue(ValueRequirementNames.HISTORICAL_TIME_SERIES);
final Object assetPnLObject = inputs.getValue(new ValueRequirement(ValueRequirementNames.PNL_SERIES, targetSpec)); //TODO replace with return series when portfolio weights are in
if (assetPnLObject == null) {
throw new OpenGammaRuntimeException("Asset P&L was null");
}
final Object assetFairValueObject = inputs.getValue(new ValueRequirement(ValueRequirementNames.FAIR_VALUE, targetSpec));
if (assetFairValueObject == null) {
throw new OpenGammaRuntimeException("Asset fair value was null");
}
final Object betaObject = inputs.getValue(new ValueRequirement(ValueRequirementNames.CAPM_BETA, targetSpec));
if (betaObject == null) {
throw new OpenGammaRuntimeException("Beta was null");
}
final double beta = (Double) betaObject;
final double fairValue = (Double) assetFairValueObject;
DoubleTimeSeries<?> assetReturnTS = ((DoubleTimeSeries<?>) assetPnLObject).divide(fairValue);
DoubleTimeSeries<?> riskFreeReturnTS = riskFreeRateTSObject.getTimeSeries().divide(100 * DAYS_PER_YEAR);
DoubleTimeSeries<?>[] series = TimeSeriesIntersector.intersect(riskFreeReturnTS, assetReturnTS);
riskFreeReturnTS = series[0];
assetReturnTS = series[1];
final TreynorRatioCalculator calculator = getCalculator(constraints.getValues(ValuePropertyNames.EXCESS_RETURN_CALCULATOR));
final double ratio = calculator.evaluate(assetReturnTS, riskFreeReturnTS, beta);
final ValueProperties resultProperties = getResultProperties(desiredValues.iterator().next());
return Sets.newHashSet(new ComputedValue(new ValueSpecification(ValueRequirementNames.TREYNOR_RATIO, targetSpec, resultProperties), ratio));
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final Set<ValueRequirement> result = new HashSet<ValueRequirement>();
final ValueProperties constraints = desiredValue.getConstraints();
final Set<String> samplingPeriodNames = constraints.getValues(ValuePropertyNames.SAMPLING_PERIOD);
if (samplingPeriodNames == null || samplingPeriodNames.size() != 1) {
return null;
}
final Set<String> scheduleCalculatorNames = constraints.getValues(ValuePropertyNames.SCHEDULE_CALCULATOR);
if (scheduleCalculatorNames == null || scheduleCalculatorNames.size() != 1) {
return null;
}
final Set<String> samplingFunctionNames = constraints.getValues(ValuePropertyNames.SAMPLING_FUNCTION);
if (samplingFunctionNames == null || samplingFunctionNames.size() != 1) {
return null;
}
final Set<String> returnCalculatorNames = constraints.getValues(ValuePropertyNames.RETURN_CALCULATOR);
if (returnCalculatorNames == null || returnCalculatorNames.size() != 1) {
return null;
}
final Set<String> stdDevCalculatorNames = constraints.getValues(ValuePropertyNames.STD_DEV_CALCULATOR);
if (stdDevCalculatorNames == null || stdDevCalculatorNames.size() != 1) {
return null;
}
final Set<String> covarianceCalculatorNames = constraints.getValues(ValuePropertyNames.COVARIANCE_CALCULATOR);
if (covarianceCalculatorNames == null || covarianceCalculatorNames.size() != 1) {
return null;
}
final Set<String> varianceCalculatorNames = constraints.getValues(ValuePropertyNames.VARIANCE_CALCULATOR);
if (varianceCalculatorNames == null || varianceCalculatorNames.size() != 1) {
return null;
}
final String samplingPeriodName = samplingPeriodNames.iterator().next();
final String scheduleCalculatorName = scheduleCalculatorNames.iterator().next();
final String samplingFunctionName = samplingFunctionNames.iterator().next();
final String returnCalculatorName = returnCalculatorNames.iterator().next();
final ValueProperties pnlSeriesProperties = ValueProperties.builder()
.withAny(ValuePropertyNames.CURRENCY)
.with(ValuePropertyNames.SAMPLING_PERIOD, samplingPeriodName)
.with(ValuePropertyNames.SCHEDULE_CALCULATOR, scheduleCalculatorName)
.with(ValuePropertyNames.SAMPLING_FUNCTION, samplingFunctionName)
.with(ValuePropertyNames.RETURN_CALCULATOR, returnCalculatorName).get();
final ValueProperties betaProperties = ValueProperties.builder()
.with(ValuePropertyNames.SAMPLING_PERIOD, samplingPeriodName)
.with(ValuePropertyNames.SCHEDULE_CALCULATOR, scheduleCalculatorName)
.with(ValuePropertyNames.SAMPLING_FUNCTION, samplingFunctionName)
.with(ValuePropertyNames.RETURN_CALCULATOR, returnCalculatorName)
.with(ValuePropertyNames.COVARIANCE_CALCULATOR, covarianceCalculatorNames.iterator().next())
.with(ValuePropertyNames.VARIANCE_CALCULATOR, varianceCalculatorNames.iterator().next()).get();
final ComputationTargetSpecification targetSpec = target.toSpecification();
result.add(new ValueRequirement(ValueRequirementNames.PNL_SERIES, targetSpec, pnlSeriesProperties));
result.add(new ValueRequirement(ValueRequirementNames.FAIR_VALUE, targetSpec));
result.add(new ValueRequirement(ValueRequirementNames.CAPM_BETA, targetSpec, betaProperties));
final HistoricalTimeSeriesResolver resolver = OpenGammaCompilationContext.getHistoricalTimeSeriesResolver(context);
final ConventionBundleSource conventionSource = OpenGammaCompilationContext.getConventionBundleSource(context);
final ConventionBundle bundle = conventionSource.getConventionBundle(ExternalId.of(InMemoryConventionBundleMaster.SIMPLE_NAME_SCHEME, "USD_CAPM"));
final HistoricalTimeSeriesResolutionResult timeSeries = resolver.resolve(bundle.getCAPMMarket(), null, null, null, MarketDataRequirementNames.MARKET_VALUE, _resolutionKey);
if (timeSeries == null) {
return null;
}
result.add(HistoricalTimeSeriesFunctionUtils.createHTSRequirement(timeSeries, MarketDataRequirementNames.MARKET_VALUE,
DateConstraint.VALUATION_TIME.minus(samplingPeriodName), true, DateConstraint.VALUATION_TIME, true));
return result;
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
if (canApplyTo(context, target)) {
return Sets.newHashSet(new ValueSpecification(ValueRequirementNames.TREYNOR_RATIO, target.toSpecification(), getResultProperties()));
}
return null;
}
private ValueProperties getResultProperties() {
return createValueProperties()
.withAny(ValuePropertyNames.SAMPLING_PERIOD)
.withAny(ValuePropertyNames.SCHEDULE_CALCULATOR)
.withAny(ValuePropertyNames.SAMPLING_FUNCTION)
.withAny(ValuePropertyNames.RETURN_CALCULATOR)
.withAny(ValuePropertyNames.STD_DEV_CALCULATOR)
.withAny(ValuePropertyNames.EXCESS_RETURN_CALCULATOR)
.withAny(ValuePropertyNames.COVARIANCE_CALCULATOR)
.withAny(ValuePropertyNames.VARIANCE_CALCULATOR).get();
}
private ValueProperties getResultProperties(final ValueRequirement desiredValue) {
return createValueProperties()
.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))
.with(ValuePropertyNames.RETURN_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.RETURN_CALCULATOR))
.with(ValuePropertyNames.STD_DEV_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.STD_DEV_CALCULATOR))
.with(ValuePropertyNames.EXCESS_RETURN_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.EXCESS_RETURN_CALCULATOR))
.with(ValuePropertyNames.COVARIANCE_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.COVARIANCE_CALCULATOR))
.with(ValuePropertyNames.VARIANCE_CALCULATOR, desiredValue.getConstraint(ValuePropertyNames.VARIANCE_CALCULATOR)).get();
}
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 TreynorRatioCalculator getCalculator(final Set<String> excessReturnCalculatorNames) {
if (excessReturnCalculatorNames == null || excessReturnCalculatorNames.isEmpty() || excessReturnCalculatorNames.size() != 1) {
throw new OpenGammaRuntimeException("Missing or non-unique excess return calculator name: " + excessReturnCalculatorNames);
}
final Function<double[], Double> expectedExcessReturnCalculator = StatisticsCalculatorFactory.getCalculator(excessReturnCalculatorNames.iterator().next());
final DoubleTimeSeriesStatisticsCalculator excessReturnCalculator = new DoubleTimeSeriesStatisticsCalculator(expectedExcessReturnCalculator);
return new TreynorRatioCalculator(excessReturnCalculator, excessReturnCalculator); //TODO check that they can both be the same
}
}